Ice3man fa56800fcc
Fuzzing layer enhancements + input-types support (#4477)
* feat: move fuzz package to root directory

* feat: added support for input providers like openapi,postman,etc

* feat: integration of new fuzzing logic in engine

* bugfix: use and instead of or

* fixed lint errors

* go mod tidy

* add new reqresp type + bump utils

* custom http request parser

* use new struct type RequestResponse

* introduce unified input/target provider

* abstract input formats via new inputprovider

* completed input provider refactor

* remove duplicated code

* add sdk method to load targets

* rename component url->path

* add new yaml format + remove duplicated code

* use gopkg.in/yaml.v3 for parsing

* update .gitignore

* refactor/move + docs fuzzing in http protocol

* fuzz: header + query integration test using fuzzplayground

* fix integration test runner in windows

* feat add support for filter in http fuzz

* rewrite header/query integration test with filter

* add replace regex rule

* support kv fuzzing + misc updates

* add path fuzzing example + misc improvements

* fix matchedURL + skip httpx on multi formats

* cookie fuzz integration test

* add json body + params body tests

* feat add multipart/form-data fuzzing support

* add all fuzz body integration test

* misc bug fixes + minor refactor

* add multipart form + body form unit tests

* only run fuzzing templates if -fuzz flag is given

* refactor/move fuzz playground server to pkg

* fix integration test + refactor

* add auth types and strategies

* add file auth provider

* start implementing auth logic in http

* add logic in http protocol

* static auth implemented for http

* default :80,:443 normalization

* feat: dynamic auth init

* feat: dynamic auth using templates

* validate targets count in openapi+swagger

* inputformats: add support to accept variables

* fix workflow integration test

* update lazy cred fetch logic

* fix unit test

* drop postman support

* domain related normalization

* update secrets.yaml file format + misc updates

* add auth prefetch option

* remove old secret files

* add fuzzing+auth related sdk options

* fix/support multiple mode in kv header fuzzing

* rename 'headers' -> 'header' in fuzzing rules

* fix deadlock due to merge conflict resolution

* misc update

* add bool type in parsed value

* add openapi validation+override+ new flags

* misc updates

* remove optional path parameters when unavailable

* fix swagger.yaml file

* misc updates

* update print msg

* multiple openapi validation enchancements + appMode

* add optional params in required_openapi_vars.yaml file

* improve warning/verbose msgs in format

* fix skip-format-validation not working

* use 'params/parameter' instead of 'variable' in openapi

* add retry support for falky tests

* fix nuclei loading ignored templates (#4849)

* fix tag include logic

* fix unit test

* remove quoting in extractor output

* remove quote in debug code command

* feat: issue tracker URLs in JSON + misc fixes (#4855)

* feat: issue tracker URLs in JSON + misc fixes

* misc changes

* feat: status update support for issues

* feat: report metadata generation hook support

* feat: added CLI summary of tickets created

* misc changes

* introduce `disable-unsigned-templates` flag (#4820)

* introduce `disable-unsigned-templates` flag

* minor

* skip instead of exit

* remove duplicate imports

* use stats package + misc enhancements

* force display warning + adjust skipped stats in unsigned count

* include unsigned skipped templates without -dut flag

---------

Co-authored-by: Tarun Koyalwar <tarun@projectdiscovery.io>

* Purge cache on global callback set (#4840)

* purge cache on global callback set

* lint

* purging cache

* purge cache in runner after loading templates

* include internal cache from parsers + add global cache register/purge via config

* remove disable cache purge option

---------

Co-authored-by: Tarun Koyalwar <tarun@projectdiscovery.io>

* misc update

* add application/octet-stream support

* openapi: support path specific params

* misc option + readme update

---------

Co-authored-by: Sandeep Singh <sandeep@projectdiscovery.io>
Co-authored-by: sandeep <8293321+ehsandeep@users.noreply.github.com>
Co-authored-by: Tarun Koyalwar <tarun@projectdiscovery.io>
Co-authored-by: Tarun Koyalwar <45962551+tarunKoyalwar@users.noreply.github.com>
Co-authored-by: Dogan Can Bakir <65292895+dogancanbakir@users.noreply.github.com>
Co-authored-by: Mzack9999 <mzack9999@protonmail.com>
2024-03-14 03:08:53 +05:30

213 lines
6.3 KiB
Go

// This package provides a mock server for testing fuzzing templates
package fuzzplayground
import (
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"os/exec"
"strconv"
"strings"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/projectdiscovery/retryablehttp-go"
)
func GetPlaygroundServer() *echo.Echo {
e := echo.New()
e.Use(middleware.Recover())
e.Use(middleware.Logger())
e.GET("/", indexHandler)
e.GET("/info", infoHandler)
e.GET("/redirect", redirectHandler)
e.GET("/request", requestHandler)
e.GET("/email", emailHandler)
e.GET("/permissions", permissionsHandler)
e.GET("/blog/post", numIdorHandler) // for num based idors like ?id=44
e.POST("/reset-password", resetPasswordHandler)
e.GET("/host-header-lab", hostHeaderLabHandler)
e.GET("/user/:id/profile", userProfileHandler)
e.POST("/user", patchUnsanitizedUserHandler)
e.GET("/blog/posts", getPostsHandler)
return e
}
var bodyTemplate = `<html>
<head>
<title>Fuzz Playground</title>
</head>
<body>
%s
</body>
</html>`
func indexHandler(ctx echo.Context) error {
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, `<h1>Fuzzing Playground</h1><hr>
<ul>
<li><a href="/info?name=test&another=value&random=data">Info Page XSS</a></li>
<li><a href="/redirect?redirect_url=/info?name=redirected_from_url">Redirect Page OpenRedirect</a></li>
<li><a href="/request?url=https://example.com">Request Page SSRF</a></li>
<li><a href="/email?text=important_user">Email Page SSTI</a></li>
<li><a href="/permissions?cmd=whoami">Permissions Page CMDI</a></li>
</ul>
`))
}
func infoHandler(ctx echo.Context) error {
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Name of user: %s%s%s", ctx.QueryParam("name"), ctx.QueryParam("another"), ctx.QueryParam("random"))))
}
func redirectHandler(ctx echo.Context) error {
url := ctx.QueryParam("redirect_url")
return ctx.Redirect(302, url)
}
func requestHandler(ctx echo.Context) error {
url := ctx.QueryParam("url")
data, err := retryablehttp.DefaultClient().Get(url)
if err != nil {
return ctx.HTML(500, err.Error())
}
defer data.Body.Close()
body, _ := io.ReadAll(data.Body)
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(body)))
}
func emailHandler(ctx echo.Context) error {
text := ctx.QueryParam("text")
if strings.Contains(text, "{{") {
trimmed := strings.SplitN(strings.Trim(text[strings.Index(text, "{"):], "{}"), "*", 2)
if len(trimmed) < 2 {
return ctx.HTML(500, "invalid template")
}
first, _ := strconv.Atoi(trimmed[0])
second, _ := strconv.Atoi(trimmed[1])
text = strconv.Itoa(first * second)
}
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Text: %s", text)))
}
func permissionsHandler(ctx echo.Context) error {
command := ctx.QueryParam("cmd")
fields := strings.Fields(command)
cmd := exec.Command(fields[0], fields[1:]...)
data, _ := cmd.CombinedOutput()
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(data)))
}
func numIdorHandler(ctx echo.Context) error {
// validate if any numerical query param is present
// if not, return 400 if so, return 200
for k := range ctx.QueryParams() {
if _, err := strconv.Atoi(ctx.QueryParam(k)); err == nil {
return ctx.JSON(200, "Profile Info for user with id "+ctx.QueryParam(k))
}
}
return ctx.JSON(400, "No numerical query param found")
}
func patchUnsanitizedUserHandler(ctx echo.Context) error {
var user User
contentType := ctx.Request().Header.Get("Content-Type")
// manually handle unmarshalling data
if strings.Contains(contentType, "application/json") {
err := ctx.Bind(&user)
if err != nil {
return ctx.JSON(500, "Invalid JSON data")
}
} else if strings.Contains(contentType, "application/x-www-form-urlencoded") {
user.Name = ctx.FormValue("name")
user.Age, _ = strconv.Atoi(ctx.FormValue("age"))
user.Role = ctx.FormValue("role")
user.ID, _ = strconv.Atoi(ctx.FormValue("id"))
} else if strings.Contains(contentType, "application/xml") {
bin, _ := io.ReadAll(ctx.Request().Body)
err := xml.Unmarshal(bin, &user)
if err != nil {
return ctx.JSON(500, "Invalid XML data")
}
} else if strings.Contains(contentType, "multipart/form-data") {
user.Name = ctx.FormValue("name")
user.Age, _ = strconv.Atoi(ctx.FormValue("age"))
user.Role = ctx.FormValue("role")
user.ID, _ = strconv.Atoi(ctx.FormValue("id"))
} else {
return ctx.JSON(500, "Invalid Content-Type")
}
err := patchUnsanitizedUser(db, user)
if err != nil {
return ctx.JSON(500, err.Error())
}
return ctx.JSON(200, "User updated successfully")
}
// resetPassword mock
func resetPasswordHandler(c echo.Context) error {
var m map[string]interface{}
if err := c.Bind(&m); err != nil {
return c.JSON(500, "Something went wrong")
}
host := c.Request().Header.Get("X-Forwarded-For")
if host == "" {
return c.JSON(500, "Something went wrong")
}
resp, err := http.Get("http://internal." + host + "/update?user=1337&pass=" + m["password"].(string))
if err != nil {
return c.JSON(500, "Something went wrong")
}
defer resp.Body.Close()
return c.JSON(200, "Password reset successfully")
}
func hostHeaderLabHandler(c echo.Context) error {
// vulnerable app has custom routing and trusts x-forwarded-host
// to route to internal services
if c.Request().Header.Get("X-Forwarded-Host") != "" {
resp, err := http.Get("http://" + c.Request().Header.Get("X-Forwarded-Host"))
if err != nil {
return c.JSON(500, "Something went wrong")
}
defer resp.Body.Close()
c.Response().Header().Set("Content-Type", resp.Header.Get("Content-Type"))
c.Response().WriteHeader(resp.StatusCode)
_, err = io.Copy(c.Response().Writer, resp.Body)
if err != nil {
return c.JSON(500, "Something went wrong")
}
}
return c.JSON(200, "Not a Teapot")
}
func userProfileHandler(ctx echo.Context) error {
val, _ := url.PathUnescape(ctx.Param("id"))
fmt.Printf("Unescaped: %s\n", val)
user, err := getUnsanitizedUser(db, val)
if err != nil {
return ctx.JSON(500, err.Error())
}
return ctx.JSON(200, user)
}
func getPostsHandler(c echo.Context) error {
lang, err := c.Cookie("lang")
if err != nil {
// If the language cookie is missing, default to English
lang = new(http.Cookie)
lang.Value = "en"
}
posts, err := getUnsanitizedPostsByLang(db, lang.Value)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, posts)
}