Repository: teknogeek/ssrf-sheriff
Branch: master
Commit: 02cd31d60970
Files: 14
Total size: 17.6 KB
Directory structure:
gitextract_dh8uha71/
├── Dockerfile
├── LICENSE.txt
├── README.md
├── config/
│ └── base.example.yaml
├── docker-compose.yml
├── generators/
│ ├── images.go
│ └── init.go
├── handler/
│ └── handler.go
├── httpserver/
│ ├── handle.go
│ ├── tcp_listener.go
│ └── wait.go
├── main.go
└── templates/
├── csv.csv
└── html.html
================================================
FILE CONTENTS
================================================
================================================
FILE: Dockerfile
================================================
FROM golang:1.21-alpine AS build-env
WORKDIR /build
RUN go mod init ssrf-sheriff
COPY . .
RUN go get -d -v ./...
RUN go build -o ssrf-sheriff .
FROM alpine:3.19
WORKDIR /app
COPY --from=build-env /build/ssrf-sheriff /usr/local/bin/ssrf-sheriff
COPY config/base.example.yaml config/base.yaml
ENTRYPOINT ["ssrf-sheriff"]
================================================
FILE: LICENSE.txt
================================================
MIT License
Copyright (c) 2019 Joel Margolis
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: README.md
================================================
# SSRF Sheriff
This is an SSRF testing sheriff written in Go. It was originally created for the [Uber H1-4420 2019 London Live Hacking Event](https://www.hackerone.com/blog/london-called-hackers-answered-recapping-h1-4420), but it is now being open-sourced for other organizations to implement and contribute back to.
## Features
- Respond to any HTTP method (`GET`, `POST`, `PUT`, `DELETE`, etc.)
- Configurable secret token (see [base.example.yaml](config/base.example.yaml))
- Content-specific responses
- With secret token in response body
- JSON
- XML
- HTML
- CSV
- TXT
- PNG
- JPEG
- Without token in response body
- GIF
- MP3
- MP4
## Usage
```bash
go get github.com/teknogeek/ssrf-sheriff
cd $GOPATH/src/github.com/teknogeek/ssrf-sheriff
cp config/base.example.yaml config/base.yaml
# ... configure ...
go run main.go
```
### Example Requests:
**Plaintext**
```
$ curl -sSD- http://127.0.0.1:8000/foobar
HTTP/1.1 200 OK
Content-Type: text/plain
X-Secret-Token: SUP3R_S3cret_1337_K3y
Date: Mon, 14 Oct 2019 16:37:36 GMT
Content-Length: 21
SUP3R_S3cret_1337_K3y
```
**XML**
```
$ curl -sSD- http://127.0.0.1:8000/foobar.xml
HTTP/1.1 200 OK
Content-Type: application/xml
X-Secret-Token: SUP3R_S3cret_1337_K3y
Date: Mon, 14 Oct 2019 16:37:41 GMT
Content-Length: 81
<SerializableResponse><token>SUP3R_S3cret_1337_K3y</token></SerializableResponse>
```
## TODO
- Dynamically generate valid responses with the secret token visible for
- GIF
- MP3
- MP4
- Secrets in HTTP response generated/created/signed per-request, instead of returning a single secret for all requests
- TLS support
## Credit
Inspired (and requested) by [Frans Rosén](https://twitter.com/fransrosen) during his [talk at BountyCon '19 Singapore](https://speakerdeck.com/fransrosen/live-hacking-like-a-mvh-a-walkthrough-on-methodology-and-strategies-to-win-big?slide=49)
-----
Released under the [MIT License](LICENSE.txt).
================================================
FILE: config/base.example.yaml
================================================
http:
address: ":8000"
ssrf_token: "REPLACE_THIS_WITH_YOUR_SECRET_VALUE"
================================================
FILE: docker-compose.yml
================================================
version: '3'
services:
ssrf_sheriff:
build: .
ports:
- "8000:8000"
================================================
FILE: generators/images.go
================================================
package generators
import (
"github.com/fogleman/gg"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font/gofont/goregular"
)
// function that generates JPG and PNG images with the provided text
// and save them into "/templates" directory
func GenerateJPGAndPNG(ssrfToken string) {
const W = 1024
const H = 768
dc := gg.NewContext(W, H)
dc.SetRGB(0, 0, 0)
dc.Clear()
dc.SetRGB(1, 1, 1)
font, err := truetype.Parse(goregular.TTF)
if err != nil {
panic("")
}
face := truetype.NewFace(font, &truetype.Options{
Size: 14,
})
dc.SetFontFace(face)
dc.DrawStringAnchored(ssrfToken, W/2, H/2, 0.5, 0.5)
dc.SaveJPG("./templates/jpeg.jpg", 80)
dc.SavePNG("./templates/png.png")
}
================================================
FILE: generators/init.go
================================================
package generators
// function that run all media files generators with the provided text
func InitMediaGenerators(ssrfToken string) {
GenerateJPGAndPNG(ssrfToken)
}
================================================
FILE: handler/handler.go
================================================
package handler
import (
"encoding/json"
"encoding/xml"
"fmt"
"io/ioutil"
"mime"
"net/http"
"path"
"path/filepath"
"github.com/gorilla/mux"
"github.com/teknogeek/ssrf-sheriff/generators"
"github.com/teknogeek/ssrf-sheriff/httpserver"
"go.uber.org/config"
"go.uber.org/fx"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// SerializableResponse is a generic type which both can be safely serialized to both XML and JSON
type SerializableResponse struct {
SecretToken string `json:"token" xml:"token"`
}
// SSRFSheriffRouter is a wrapper around mux.Router to handle HTTP requests to the sheriff, with logging
type SSRFSheriffRouter struct {
logger *zap.Logger
ssrfToken string
}
// NewHTTPServer provides a new HTTP server listener
func NewHTTPServer(
mux *mux.Router,
cfg config.Provider,
) *http.Server {
return &http.Server{
Addr: cfg.Get("http.address").String(),
Handler: mux,
}
}
// NewSSRFSheriffRouter returns a new SSRFSheriffRouter which is used to route and handle all HTTP requests
func NewSSRFSheriffRouter(
logger *zap.Logger,
cfg config.Provider,
) *SSRFSheriffRouter {
return &SSRFSheriffRouter{
logger: logger,
ssrfToken: cfg.Get("ssrf_token").String(),
}
}
// StartFilesGenerator starts the function which is dynamically generating JPG/PNG formats
// with the secret token rendered in the media
func StartFilesGenerator(cfg config.Provider) {
generators.InitMediaGenerators(cfg.Get("ssrf_token").String())
}
// StartServer starts the HTTP server
func StartServer(server *http.Server, lc fx.Lifecycle) {
h := httpserver.NewHandle(server)
lc.Append(fx.Hook{
OnStart: h.Start,
OnStop: h.Shutdown,
})
}
// PathHandler is the main handler for all inbound requests
func (s *SSRFSheriffRouter) PathHandler(w http.ResponseWriter, r *http.Request) {
fileExtension := filepath.Ext(r.URL.Path)
contentType := mime.TypeByExtension(fileExtension)
var response string
switch fileExtension {
case ".json":
res, _ := json.Marshal(SerializableResponse{SecretToken: s.ssrfToken})
response = string(res)
case ".xml":
res, _ := xml.Marshal(SerializableResponse{SecretToken: s.ssrfToken})
response = string(res)
case ".html":
tmpl := readTemplateFile("html.html")
response = fmt.Sprintf(tmpl, s.ssrfToken, s.ssrfToken)
case ".csv":
tmpl := readTemplateFile("csv.csv")
response = fmt.Sprintf(tmpl, s.ssrfToken)
case ".txt":
response = fmt.Sprintf("token=%s", s.ssrfToken)
case ".png":
response = readTemplateFile("png.png")
case ".jpg", ".jpeg":
response = readTemplateFile("jpeg.jpg")
// TODO: dynamically generate these formats with the secret token rendered in the media
case ".gif":
response = readTemplateFile("gif.gif")
case ".mp3":
response = readTemplateFile("mp3.mp3")
case ".mp4":
response = readTemplateFile("mp4.mp4")
default:
response = s.ssrfToken
}
if contentType == "" {
contentType = "text/plain"
}
s.logger.Info("New inbound HTTP request",
zap.String("IP", r.RemoteAddr),
zap.String("Path", r.URL.Path),
zap.String("Response Content-Type", contentType),
zap.Any("Request Headers", r.Header),
)
responseBytes := []byte(response)
w.Header().Set("Content-Type", contentType)
w.Header().Set("X-Secret-Token", s.ssrfToken)
w.WriteHeader(http.StatusOK)
w.Write(responseBytes)
}
func readTemplateFile(templateFileName string) string {
data, err := ioutil.ReadFile(path.Join("templates", path.Clean(templateFileName)))
if err != nil {
return ""
}
return string(data)
}
// NewServerRouter returns a new mux.Router for handling any HTTP request to /.*
func NewServerRouter(s *SSRFSheriffRouter) *mux.Router {
router := mux.NewRouter()
router.PathPrefix("/").HandlerFunc(s.PathHandler)
return router
}
// NewConfigProvider returns a config.Provider for YAML configuration
func NewConfigProvider() (config.Provider, error) {
return config.NewYAMLProviderFromFiles("config/base.yaml")
}
// NewLogger returns a new *zap.Logger
func NewLogger() (*zap.Logger, error) {
zapConfig := zap.NewProductionConfig()
zapConfig.Encoding = "console"
zapConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
zapConfig.DisableStacktrace = true
return zapConfig.Build()
}
================================================
FILE: httpserver/handle.go
================================================
package httpserver
import (
"context"
"errors"
"fmt"
"net"
"net/http"
)
// HandleOption customizes the behavior of a Handle.
type HandleOption interface {
apply(*Handle)
}
type handleOptionFunc func(*Handle)
func (f handleOptionFunc) apply(h *Handle) { f(h) }
// ListenFunc is an option for Handle that allows changing how it listens for
// incoming connections.
func ListenFunc(f func(string, string) (net.Listener, error)) HandleOption {
return handleOptionFunc(func(h *Handle) {
h.listenFunc = f
})
}
// DefaultListenFunc builds a net.Listener with the given network and address.
// This function is the default value for ListenFunc.
func DefaultListenFunc(network, address string) (net.Listener, error) {
ln, err := net.Listen(network, address)
// keep-alive on all TCP connections. net/http's ListenAndServe and
// ListenAndServeTLS do this by default but not Server.Serve(..).
if tcpListener, ok := ln.(*net.TCPListener); ok {
ln = tcpKeepAliveListener{tcpListener}
}
return ln, err
}
func newDialer() dialer { return new(net.Dialer) }
// Changes how we build dialers.
//
// This is an unexported option used for testing only.
func newDialerFunc(f func() dialer) HandleOption {
return handleOptionFunc(func(h *Handle) {
h.newDialerFunc = f
})
}
// Handle is a reference to an HTTP server. It provides clean startup and
// shutdown for net/http HTTP servers.
type Handle struct {
// HTTP server provided by the user.
srv *http.Server
// Listener we're listening on (if any). This is nil if Start hasn't been
// called yet.
ln net.Listener
// errCh will be filled with the error returned by http.Server.Serve.
errCh chan error
// Function used to create net.Listeners. Defaults to net.Listen.
listenFunc func(string, string) (net.Listener, error)
// Function used to build dialers. Defaults to newDialer.
newDialerFunc func() dialer
}
// NewHandle builds a Handle to the given HTTP server. You can use the
// returned Handle to start the server and access information about the
// running server.
//
// Handle must be used for all server operations from this point onwards.
// Starting or stopping the http.Server directly will lead to undefined
// behavior.
//
// Note that Handle is not thread-safe. You must not call procedures on Handle
// concurrently.
func NewHandle(srv *http.Server, opts ...HandleOption) *Handle {
h := &Handle{
srv: srv,
listenFunc: DefaultListenFunc,
newDialerFunc: newDialer,
}
for _, opt := range opts {
opt.apply(h)
}
return h
}
// Addr returns the address on which the HTTP server is listening. This can be
// used to determine the address of the server if it was started on an
// OS-assigned port (":0").
//
// Returns nil if the server hasn't been started yet.
func (h *Handle) Addr() net.Addr {
if h.ln == nil {
return nil
}
return h.ln.Addr()
}
// Start starts the HTTP server for this Handle in a separate goroutine and
// blocks until the server is ready to accept requests or the provided context
// finishes.
//
// The server is started on the address defined on Server.Addr, defaulting to
// an OS-assigned port (":0") if Server.Addr is empty.
//
// h := httpserver.NewHandle(&http.Server{Handler: myHandler})
// err := h.Start(ctx)
//
// Note that because the server is started in a separate goroutine, this
// method is safe to use as-is inside Fx Lifecycle hooks.
//
// fx.Hook{
// OnStart: handle.Start,
// OnStop: handle.Shutdown,
// }
func (h *Handle) Start(ctx context.Context) error {
if h.ln != nil {
return errors.New("server is already running")
}
// http.Server defaults to ":http" if Addr is empty. For our purposes,
// ":0" is more desirable since we almost never listen on port 80.
addr := h.srv.Addr
if addr == "" {
addr = ":0"
}
// Most errors that occur when starting an http.Server are actually Listen
// errors. If we encounter one of those, we can abort immediately.
ln, err := h.listenFunc("tcp", addr)
if err != nil {
return fmt.Errorf("error starting HTTP server on %q: %v", addr, err)
}
errCh := make(chan error, 1)
go func() {
// Serve blocks until it encounters an error or until the server shuts
// down, so we need to call it in a separate goroutine. Errors here
// (apart from http.ErrServerClosed) are rare.
err := h.srv.Serve(ln)
errCh <- err
// Close the channel so that if shutdown is called on this Handle
// again, it doesn't wait on the channel indefinitely.
close(errCh)
}()
// We wait until the server is ready to process requests.
//
// We would normally be able to return after starting the listener but
// that introduces a very annoying race condition:
//
// Consider,
//
// err := h.Start(..)
// h.Shutdown(ctx)
//
// If srv.Shutdown gets invoked before the goroutine that is calling
// srv.Serve has transitioned the server to the running state,
// srv.Shutdown will return right away but srv.Serve will run forever.
d := h.newDialerFunc()
if err := waitUntilAvailable(ctx, d, ln.Addr().String()); err != nil {
select {
case err := <-errCh:
// If the server failed to start up, errCh probably has a more
// helpful error.
return fmt.Errorf("error starting HTTP server: %v", err)
default:
// Kill the listener if we failed to start the server up.
//
// We don't need to do this for the errCh path because having a
// value in errCh indicates that Serve finished running, and Serve
// always closes the listener.
ln.Close()
return wrapNetErr(err, "error waiting for server to start up")
}
}
h.errCh = errCh
h.ln = ln
return nil
}
// Shutdown initiates a graceful shutdown of the HTTP server. The provided
// context controls how long we are willing to wait for the server to shut
// down. Shutdown will block until the server has shut down completely or
// until the context finishes.
func (h *Handle) Shutdown(ctx context.Context) error {
if err := h.srv.Shutdown(ctx); err != nil {
return err
}
if err := <-h.errCh; err != http.ErrServerClosed {
return err
}
h.ln = nil
return nil
}
================================================
FILE: httpserver/tcp_listener.go
================================================
package httpserver
import (
"net"
"time"
)
// Copied from https://github.com/golang/go/blob/fcee1897767c0cfa6e13a843fe5ee5d1deb8081b/src/net/http/server.go#L3156-L3172
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
================================================
FILE: httpserver/wait.go
================================================
package httpserver
import (
"context"
"fmt"
"net"
)
var _invalidHTTPRequestLine = []byte("INVALID\n\n")
// Subset of the net.Dialer API that we care about.
type dialer interface {
DialContext(context.Context, string, string) (net.Conn, error)
}
var _ dialer = (*net.Dialer)(nil)
// waitUntilAvailable uses the given dialer to connect to the HTTP server at
// the provided address and waits until the server is ready to accept requests
// or the given context times out.
//
// This works by sending an invalid request line to the server and waiting for
// a response. The request line is the "GET /index.html HTTP/1.1" part of an
// HTTP request. Instead of sending a valid one which could end up calling the
// user-provided request handler, we send one that will be rejected by the
// HTTP server implementation without crashing.
func waitUntilAvailable(ctx context.Context, d dialer, addr string) error {
conn, err := d.DialContext(ctx, "tcp", addr)
if err != nil {
return wrapNetErr(err, "failed to dial to %q", addr)
}
defer conn.Close()
if deadline, ok := ctx.Deadline(); ok {
// DialContext applies the timeout only to establishing the
// connection. Here we're applying the same deadline to the rest of
// this TCP conversation.
if err := conn.SetDeadline(deadline); err != nil {
return fmt.Errorf("failed to set connection deadline to %v: %v", deadline, err)
}
}
if _, err := conn.Write(_invalidHTTPRequestLine); err != nil {
return wrapNetErr(err, "failed to write request to server")
}
// Once we receive a single byte from the server, we know that the server
// is processing HTTP requests.
var out [1]byte
if _, err := conn.Read(out[:]); err != nil {
return wrapNetErr(err, "failed to read response from server")
}
return nil
}
// Similar to fmt.Errorf except net.Error timeouts are translated to
// context.DeadlineExceeded.
func wrapNetErr(err error, msg string, args ...interface{}) error {
if err == nil {
return nil
}
if ne, ok := err.(net.Error); ok && ne.Timeout() {
return context.DeadlineExceeded
}
if len(args) > 0 {
msg = fmt.Sprintf(msg, args...)
}
return fmt.Errorf("%s: %v", msg, err)
}
================================================
FILE: main.go
================================================
package main
import (
"github.com/teknogeek/ssrf-sheriff/handler"
"go.uber.org/fx"
)
func main() {
fx.New(opts()).Run()
}
func opts() fx.Option {
return fx.Options(
fx.Provide(
handler.NewLogger,
handler.NewConfigProvider,
handler.NewSSRFSheriffRouter,
handler.NewServerRouter,
handler.NewHTTPServer,
),
fx.Invoke(handler.StartFilesGenerator, handler.StartServer),
)
}
================================================
FILE: templates/csv.csv
================================================
key,value
token,%s
================================================
FILE: templates/html.html
================================================
<!DOCTYPE html><html><head><title>token=%s</title></head><body>token=%s</body></html>
gitextract_dh8uha71/
├── Dockerfile
├── LICENSE.txt
├── README.md
├── config/
│ └── base.example.yaml
├── docker-compose.yml
├── generators/
│ ├── images.go
│ └── init.go
├── handler/
│ └── handler.go
├── httpserver/
│ ├── handle.go
│ ├── tcp_listener.go
│ └── wait.go
├── main.go
└── templates/
├── csv.csv
└── html.html
SYMBOL INDEX (32 symbols across 7 files)
FILE: generators/images.go
function GenerateJPGAndPNG (line 11) | func GenerateJPGAndPNG(ssrfToken string) {
FILE: generators/init.go
function InitMediaGenerators (line 4) | func InitMediaGenerators(ssrfToken string) {
FILE: handler/handler.go
type SerializableResponse (line 23) | type SerializableResponse struct
type SSRFSheriffRouter (line 28) | type SSRFSheriffRouter struct
method PathHandler (line 72) | func (s *SSRFSheriffRouter) PathHandler(w http.ResponseWriter, r *http...
function NewHTTPServer (line 34) | func NewHTTPServer(
function NewSSRFSheriffRouter (line 46) | func NewSSRFSheriffRouter(
function StartFilesGenerator (line 58) | func StartFilesGenerator(cfg config.Provider) {
function StartServer (line 63) | func StartServer(server *http.Server, lc fx.Lifecycle) {
function readTemplateFile (line 125) | func readTemplateFile(templateFileName string) string {
function NewServerRouter (line 134) | func NewServerRouter(s *SSRFSheriffRouter) *mux.Router {
function NewConfigProvider (line 141) | func NewConfigProvider() (config.Provider, error) {
function NewLogger (line 146) | func NewLogger() (*zap.Logger, error) {
FILE: httpserver/handle.go
type HandleOption (line 12) | type HandleOption interface
type handleOptionFunc (line 16) | type handleOptionFunc
method apply (line 18) | func (f handleOptionFunc) apply(h *Handle) { f(h) }
function ListenFunc (line 22) | func ListenFunc(f func(string, string) (net.Listener, error)) HandleOpti...
function DefaultListenFunc (line 30) | func DefaultListenFunc(network, address string) (net.Listener, error) {
function newDialer (line 42) | func newDialer() dialer { return new(net.Dialer) }
function newDialerFunc (line 47) | func newDialerFunc(f func() dialer) HandleOption {
type Handle (line 55) | type Handle struct
method Addr (line 102) | func (h *Handle) Addr() net.Addr {
method Start (line 126) | func (h *Handle) Start(ctx context.Context) error {
method Shutdown (line 198) | func (h *Handle) Shutdown(ctx context.Context) error {
function NewHandle (line 83) | func NewHandle(srv *http.Server, opts ...HandleOption) *Handle {
FILE: httpserver/tcp_listener.go
type tcpKeepAliveListener (line 14) | type tcpKeepAliveListener struct
method Accept (line 18) | func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
FILE: httpserver/wait.go
type dialer (line 12) | type dialer interface
function waitUntilAvailable (line 27) | func waitUntilAvailable(ctx context.Context, d dialer, addr string) error {
function wrapNetErr (line 59) | func wrapNetErr(err error, msg string, args ...interface{}) error {
FILE: main.go
function main (line 8) | func main() {
function opts (line 12) | func opts() fx.Option {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (20K chars).
[
{
"path": "Dockerfile",
"chars": 323,
"preview": "FROM golang:1.21-alpine AS build-env\n\nWORKDIR /build\nRUN go mod init ssrf-sheriff\nCOPY . .\nRUN go get -d -v ./...\nRUN go"
},
{
"path": "LICENSE.txt",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2019 Joel Margolis\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 1966,
"preview": "# SSRF Sheriff\n\nThis is an SSRF testing sheriff written in Go. It was originally created for the [Uber H1-4420 2019 Lond"
},
{
"path": "config/base.example.yaml",
"chars": 76,
"preview": "http:\n address: \":8000\"\n\nssrf_token: \"REPLACE_THIS_WITH_YOUR_SECRET_VALUE\"\n"
},
{
"path": "docker-compose.yml",
"chars": 83,
"preview": "version: '3'\nservices:\n ssrf_sheriff:\n build: .\n ports:\n - \"8000:8000\"\n"
},
{
"path": "generators/images.go",
"chars": 707,
"preview": "package generators\n\nimport (\n\t\"github.com/fogleman/gg\"\n\t\"github.com/golang/freetype/truetype\"\n\t\"golang.org/x/image/font/"
},
{
"path": "generators/init.go",
"chars": 169,
"preview": "package generators\n\n// function that run all media files generators with the provided text\nfunc InitMediaGenerators(ssrf"
},
{
"path": "handler/handler.go",
"chars": 4211,
"preview": "package handler\n\nimport (\n\t\"encoding/json\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"mime\"\n\t\"net/http\"\n\t\"path\"\n\t\"path/filepa"
},
{
"path": "httpserver/handle.go",
"chars": 6089,
"preview": "package httpserver\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n)\n\n// HandleOption customizes the behavior o"
},
{
"path": "httpserver/tcp_listener.go",
"chars": 648,
"preview": "package httpserver\n\nimport (\n\t\"net\"\n\t\"time\"\n)\n\n// Copied from https://github.com/golang/go/blob/fcee1897767c0cfa6e13a843"
},
{
"path": "httpserver/wait.go",
"chars": 2176,
"preview": "package httpserver\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n)\n\nvar _invalidHTTPRequestLine = []byte(\"INVALID\\n\\n\")\n\n// Subset "
},
{
"path": "main.go",
"chars": 398,
"preview": "package main\n\nimport (\n\t\"github.com/teknogeek/ssrf-sheriff/handler\"\n\t\"go.uber.org/fx\"\n)\n\nfunc main() {\n\tfx.New(opts()).R"
},
{
"path": "templates/csv.csv",
"chars": 19,
"preview": "key,value\ntoken,%s\n"
},
{
"path": "templates/html.html",
"chars": 86,
"preview": "<!DOCTYPE html><html><head><title>token=%s</title></head><body>token=%s</body></html>\n"
}
]
About this extraction
This page contains the full source code of the teknogeek/ssrf-sheriff GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (17.6 KB), approximately 5.0k tokens, and a symbol index with 32 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.