Path: blob/dev/pkg/testutils/fuzzplayground/server.go
2070 views
// This package provides a mock server for testing fuzzing templates1package fuzzplayground23import (4"encoding/xml"5"fmt"6"io"7"net/http"8"net/url"9"os/exec"10"strconv"11"strings"1213"github.com/labstack/echo/v4"14"github.com/labstack/echo/v4/middleware"15"github.com/projectdiscovery/retryablehttp-go"16)1718func GetPlaygroundServer() *echo.Echo {19e := echo.New()20e.Use(middleware.Recover())21e.Use(middleware.Logger())2223e.GET("/", indexHandler)24e.GET("/info", infoHandler)25e.GET("/redirect", redirectHandler)26e.GET("/request", requestHandler)27e.GET("/email", emailHandler)28e.GET("/permissions", permissionsHandler)2930e.GET("/blog/post", numIdorHandler) // for num based idors like ?id=4431e.POST("/reset-password", resetPasswordHandler)32e.GET("/host-header-lab", hostHeaderLabHandler)33e.GET("/user/:id/profile", userProfileHandler)34e.POST("/user", patchUnsanitizedUserHandler)35e.GET("/blog/posts", getPostsHandler)36return e37}3839var bodyTemplate = `<html>40<head>41<title>Fuzz Playground</title>42</head>43<body>44%s45</body>46</html>`4748func indexHandler(ctx echo.Context) error {49return ctx.HTML(200, fmt.Sprintf(bodyTemplate, `<h1>Fuzzing Playground</h1><hr>50<ul>5152<li><a href="/info?name=test&another=value&random=data">Info Page XSS</a></li>53<li><a href="/redirect?redirect_url=/info?name=redirected_from_url">Redirect Page OpenRedirect</a></li>54<li><a href="/request?url=https://example.com">Request Page SSRF</a></li>55<li><a href="/email?text=important_user">Email Page SSTI</a></li>56<li><a href="/permissions?cmd=whoami">Permissions Page CMDI</a></li>5758<li><a href="/host-header-lab">Host Header Lab (X-Forwarded-Host Trusted)</a></li>59<li><a href="/user/75/profile">User Profile Page SQLI (path parameter)</a></li>60<li><a href="/user">POST on /user SQLI (body parameter)</a></li>61<li><a href="/blog/posts">SQLI in cookie lang parameter value (eg. lang=en)</a></li>6263</ul>64`))65}6667func infoHandler(ctx echo.Context) error {68return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Name of user: %s%s%s", ctx.QueryParam("name"), ctx.QueryParam("another"), ctx.QueryParam("random"))))69}7071func redirectHandler(ctx echo.Context) error {72url := ctx.QueryParam("redirect_url")73return ctx.Redirect(302, url)74}7576func requestHandler(ctx echo.Context) error {77url := ctx.QueryParam("url")78data, err := retryablehttp.DefaultClient().Get(url)79if err != nil {80return ctx.HTML(500, err.Error())81}82defer func() {83_ = data.Body.Close()84}()8586body, _ := io.ReadAll(data.Body)87return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(body)))88}8990func emailHandler(ctx echo.Context) error {91text := ctx.QueryParam("text")92if strings.Contains(text, "{{") {93trimmed := strings.SplitN(strings.Trim(text[strings.Index(text, "{"):], "{}"), "*", 2)94if len(trimmed) < 2 {95return ctx.HTML(500, "invalid template")96}97first, _ := strconv.Atoi(trimmed[0])98second, _ := strconv.Atoi(trimmed[1])99text = strconv.Itoa(first * second)100}101return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Text: %s", text)))102}103104func permissionsHandler(ctx echo.Context) error {105command := ctx.QueryParam("cmd")106fields := strings.Fields(command)107cmd := exec.Command(fields[0], fields[1:]...)108data, _ := cmd.CombinedOutput()109110return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(data)))111}112113func numIdorHandler(ctx echo.Context) error {114// validate if any numerical query param is present115// if not, return 400 if so, return 200116for k := range ctx.QueryParams() {117if _, err := strconv.Atoi(ctx.QueryParam(k)); err == nil {118return ctx.JSON(200, "Profile Info for user with id "+ctx.QueryParam(k))119}120}121return ctx.JSON(400, "No numerical query param found")122}123124func patchUnsanitizedUserHandler(ctx echo.Context) error {125var user User126127contentType := ctx.Request().Header.Get("Content-Type")128// manually handle unmarshalling data129if strings.Contains(contentType, "application/json") {130err := ctx.Bind(&user)131if err != nil {132return ctx.JSON(500, "Invalid JSON data")133}134} else if strings.Contains(contentType, "application/x-www-form-urlencoded") {135user.Name = ctx.FormValue("name")136user.Age, _ = strconv.Atoi(ctx.FormValue("age"))137user.Role = ctx.FormValue("role")138user.ID, _ = strconv.Atoi(ctx.FormValue("id"))139} else if strings.Contains(contentType, "application/xml") {140bin, _ := io.ReadAll(ctx.Request().Body)141err := xml.Unmarshal(bin, &user)142if err != nil {143return ctx.JSON(500, "Invalid XML data")144}145} else if strings.Contains(contentType, "multipart/form-data") {146user.Name = ctx.FormValue("name")147user.Age, _ = strconv.Atoi(ctx.FormValue("age"))148user.Role = ctx.FormValue("role")149user.ID, _ = strconv.Atoi(ctx.FormValue("id"))150} else {151return ctx.JSON(500, "Invalid Content-Type")152}153154err := patchUnsanitizedUser(db, user)155if err != nil {156return ctx.JSON(500, err.Error())157}158return ctx.JSON(200, "User updated successfully")159}160161// resetPassword mock162func resetPasswordHandler(c echo.Context) error {163var m map[string]interface{}164if err := c.Bind(&m); err != nil {165return c.JSON(500, "Something went wrong")166}167168host := c.Request().Header.Get("X-Forwarded-For")169if host == "" {170return c.JSON(500, "Something went wrong")171}172resp, err := http.Get("http://internal." + host + "/update?user=1337&pass=" + m["password"].(string))173if err != nil {174return c.JSON(500, "Something went wrong")175}176defer func() {177_ = resp.Body.Close()178}()179return c.JSON(200, "Password reset successfully")180}181182func hostHeaderLabHandler(c echo.Context) error {183// vulnerable app has custom routing and trusts x-forwarded-host184// to route to internal services185if c.Request().Header.Get("X-Forwarded-Host") != "" {186resp, err := http.Get("http://" + c.Request().Header.Get("X-Forwarded-Host"))187if err != nil {188return c.JSON(500, "Something went wrong")189}190defer func() {191_ = resp.Body.Close()192}()193c.Response().Header().Set("Content-Type", resp.Header.Get("Content-Type"))194c.Response().WriteHeader(resp.StatusCode)195_, err = io.Copy(c.Response().Writer, resp.Body)196if err != nil {197return c.JSON(500, "Something went wrong")198}199}200return c.JSON(200, "Not a Teapot")201}202203func userProfileHandler(ctx echo.Context) error {204val, _ := url.PathUnescape(ctx.Param("id"))205fmt.Printf("Unescaped: %s\n", val)206user, err := getUnsanitizedUser(db, val)207if err != nil {208return ctx.JSON(500, err.Error())209}210return ctx.JSON(200, user)211}212213func getPostsHandler(c echo.Context) error {214lang, err := c.Cookie("lang")215if err != nil {216// If the language cookie is missing, default to English217lang = new(http.Cookie)218lang.Value = "en"219}220posts, err := getUnsanitizedPostsByLang(db, lang.Value)221if err != nil {222return c.JSON(http.StatusInternalServerError, err.Error())223}224return c.JSON(http.StatusOK, posts)225}226227228