Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/testutils/fuzzplayground/server.go
2070 views
1
// This package provides a mock server for testing fuzzing templates
2
package fuzzplayground
3
4
import (
5
"encoding/xml"
6
"fmt"
7
"io"
8
"net/http"
9
"net/url"
10
"os/exec"
11
"strconv"
12
"strings"
13
14
"github.com/labstack/echo/v4"
15
"github.com/labstack/echo/v4/middleware"
16
"github.com/projectdiscovery/retryablehttp-go"
17
)
18
19
func GetPlaygroundServer() *echo.Echo {
20
e := echo.New()
21
e.Use(middleware.Recover())
22
e.Use(middleware.Logger())
23
24
e.GET("/", indexHandler)
25
e.GET("/info", infoHandler)
26
e.GET("/redirect", redirectHandler)
27
e.GET("/request", requestHandler)
28
e.GET("/email", emailHandler)
29
e.GET("/permissions", permissionsHandler)
30
31
e.GET("/blog/post", numIdorHandler) // for num based idors like ?id=44
32
e.POST("/reset-password", resetPasswordHandler)
33
e.GET("/host-header-lab", hostHeaderLabHandler)
34
e.GET("/user/:id/profile", userProfileHandler)
35
e.POST("/user", patchUnsanitizedUserHandler)
36
e.GET("/blog/posts", getPostsHandler)
37
return e
38
}
39
40
var bodyTemplate = `<html>
41
<head>
42
<title>Fuzz Playground</title>
43
</head>
44
<body>
45
%s
46
</body>
47
</html>`
48
49
func indexHandler(ctx echo.Context) error {
50
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, `<h1>Fuzzing Playground</h1><hr>
51
<ul>
52
53
<li><a href="/info?name=test&another=value&random=data">Info Page XSS</a></li>
54
<li><a href="/redirect?redirect_url=/info?name=redirected_from_url">Redirect Page OpenRedirect</a></li>
55
<li><a href="/request?url=https://example.com">Request Page SSRF</a></li>
56
<li><a href="/email?text=important_user">Email Page SSTI</a></li>
57
<li><a href="/permissions?cmd=whoami">Permissions Page CMDI</a></li>
58
59
<li><a href="/host-header-lab">Host Header Lab (X-Forwarded-Host Trusted)</a></li>
60
<li><a href="/user/75/profile">User Profile Page SQLI (path parameter)</a></li>
61
<li><a href="/user">POST on /user SQLI (body parameter)</a></li>
62
<li><a href="/blog/posts">SQLI in cookie lang parameter value (eg. lang=en)</a></li>
63
64
</ul>
65
`))
66
}
67
68
func infoHandler(ctx echo.Context) error {
69
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Name of user: %s%s%s", ctx.QueryParam("name"), ctx.QueryParam("another"), ctx.QueryParam("random"))))
70
}
71
72
func redirectHandler(ctx echo.Context) error {
73
url := ctx.QueryParam("redirect_url")
74
return ctx.Redirect(302, url)
75
}
76
77
func requestHandler(ctx echo.Context) error {
78
url := ctx.QueryParam("url")
79
data, err := retryablehttp.DefaultClient().Get(url)
80
if err != nil {
81
return ctx.HTML(500, err.Error())
82
}
83
defer func() {
84
_ = data.Body.Close()
85
}()
86
87
body, _ := io.ReadAll(data.Body)
88
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(body)))
89
}
90
91
func emailHandler(ctx echo.Context) error {
92
text := ctx.QueryParam("text")
93
if strings.Contains(text, "{{") {
94
trimmed := strings.SplitN(strings.Trim(text[strings.Index(text, "{"):], "{}"), "*", 2)
95
if len(trimmed) < 2 {
96
return ctx.HTML(500, "invalid template")
97
}
98
first, _ := strconv.Atoi(trimmed[0])
99
second, _ := strconv.Atoi(trimmed[1])
100
text = strconv.Itoa(first * second)
101
}
102
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Text: %s", text)))
103
}
104
105
func permissionsHandler(ctx echo.Context) error {
106
command := ctx.QueryParam("cmd")
107
fields := strings.Fields(command)
108
cmd := exec.Command(fields[0], fields[1:]...)
109
data, _ := cmd.CombinedOutput()
110
111
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(data)))
112
}
113
114
func numIdorHandler(ctx echo.Context) error {
115
// validate if any numerical query param is present
116
// if not, return 400 if so, return 200
117
for k := range ctx.QueryParams() {
118
if _, err := strconv.Atoi(ctx.QueryParam(k)); err == nil {
119
return ctx.JSON(200, "Profile Info for user with id "+ctx.QueryParam(k))
120
}
121
}
122
return ctx.JSON(400, "No numerical query param found")
123
}
124
125
func patchUnsanitizedUserHandler(ctx echo.Context) error {
126
var user User
127
128
contentType := ctx.Request().Header.Get("Content-Type")
129
// manually handle unmarshalling data
130
if strings.Contains(contentType, "application/json") {
131
err := ctx.Bind(&user)
132
if err != nil {
133
return ctx.JSON(500, "Invalid JSON data")
134
}
135
} else if strings.Contains(contentType, "application/x-www-form-urlencoded") {
136
user.Name = ctx.FormValue("name")
137
user.Age, _ = strconv.Atoi(ctx.FormValue("age"))
138
user.Role = ctx.FormValue("role")
139
user.ID, _ = strconv.Atoi(ctx.FormValue("id"))
140
} else if strings.Contains(contentType, "application/xml") {
141
bin, _ := io.ReadAll(ctx.Request().Body)
142
err := xml.Unmarshal(bin, &user)
143
if err != nil {
144
return ctx.JSON(500, "Invalid XML data")
145
}
146
} else if strings.Contains(contentType, "multipart/form-data") {
147
user.Name = ctx.FormValue("name")
148
user.Age, _ = strconv.Atoi(ctx.FormValue("age"))
149
user.Role = ctx.FormValue("role")
150
user.ID, _ = strconv.Atoi(ctx.FormValue("id"))
151
} else {
152
return ctx.JSON(500, "Invalid Content-Type")
153
}
154
155
err := patchUnsanitizedUser(db, user)
156
if err != nil {
157
return ctx.JSON(500, err.Error())
158
}
159
return ctx.JSON(200, "User updated successfully")
160
}
161
162
// resetPassword mock
163
func resetPasswordHandler(c echo.Context) error {
164
var m map[string]interface{}
165
if err := c.Bind(&m); err != nil {
166
return c.JSON(500, "Something went wrong")
167
}
168
169
host := c.Request().Header.Get("X-Forwarded-For")
170
if host == "" {
171
return c.JSON(500, "Something went wrong")
172
}
173
resp, err := http.Get("http://internal." + host + "/update?user=1337&pass=" + m["password"].(string))
174
if err != nil {
175
return c.JSON(500, "Something went wrong")
176
}
177
defer func() {
178
_ = resp.Body.Close()
179
}()
180
return c.JSON(200, "Password reset successfully")
181
}
182
183
func hostHeaderLabHandler(c echo.Context) error {
184
// vulnerable app has custom routing and trusts x-forwarded-host
185
// to route to internal services
186
if c.Request().Header.Get("X-Forwarded-Host") != "" {
187
resp, err := http.Get("http://" + c.Request().Header.Get("X-Forwarded-Host"))
188
if err != nil {
189
return c.JSON(500, "Something went wrong")
190
}
191
defer func() {
192
_ = resp.Body.Close()
193
}()
194
c.Response().Header().Set("Content-Type", resp.Header.Get("Content-Type"))
195
c.Response().WriteHeader(resp.StatusCode)
196
_, err = io.Copy(c.Response().Writer, resp.Body)
197
if err != nil {
198
return c.JSON(500, "Something went wrong")
199
}
200
}
201
return c.JSON(200, "Not a Teapot")
202
}
203
204
func userProfileHandler(ctx echo.Context) error {
205
val, _ := url.PathUnescape(ctx.Param("id"))
206
fmt.Printf("Unescaped: %s\n", val)
207
user, err := getUnsanitizedUser(db, val)
208
if err != nil {
209
return ctx.JSON(500, err.Error())
210
}
211
return ctx.JSON(200, user)
212
}
213
214
func getPostsHandler(c echo.Context) error {
215
lang, err := c.Cookie("lang")
216
if err != nil {
217
// If the language cookie is missing, default to English
218
lang = new(http.Cookie)
219
lang.Value = "en"
220
}
221
posts, err := getUnsanitizedPostsByLang(db, lang.Value)
222
if err != nil {
223
return c.JSON(http.StatusInternalServerError, err.Error())
224
}
225
return c.JSON(http.StatusOK, posts)
226
}
227
228