diff options
| author | Shulhan <ms@kilabit.info> | 2021-03-27 19:08:41 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2021-03-27 19:08:41 +0700 |
| commit | a098c7d1641299e14ddfa88800922c128feff2d0 (patch) | |
| tree | bf0729b0de68654db1b4d64ec7cb47454d03b4f4 | |
| parent | 413ed58f7df58644f030ddf70cbd3b234270e579 (diff) | |
| download | gorankusu-a098c7d1641299e14ddfa88800922c128feff2d0.tar.xz | |
all: allow client to customize request method, path, and type
Previously, the HttpTarget Method, Path, and RequestType is fixed.
Once its declared, the Run method will use the defined values to
generate and call HTTP request.
This changes add IsCustomizable field on HttpTarget. If its true,
client or web user interface can modify the request method, path, and
type. The values send by client to server after changes will be used
instead of the fixed values.
This changes also make the Run handler on HttpTarget to be optional.
If its not defined, it will generated the HTTP request from the method,
path, and type passed to server.
| -rw-r--r-- | _www/index.js | 122 | ||||
| -rw-r--r-- | example/example.go | 31 | ||||
| -rw-r--r-- | http_target.go | 4 | ||||
| -rw-r--r-- | key_value.go | 15 | ||||
| -rw-r--r-- | trunks.go | 106 |
5 files changed, 252 insertions, 26 deletions
diff --git a/_www/index.js b/_www/index.js index 1924c06..5512b14 100644 --- a/_www/index.js +++ b/_www/index.js @@ -153,10 +153,10 @@ function renderTarget(targetID) { } function renderHttpTargets(target) { - let w = ""; + let w = "" if (!target.HttpTargets) { - return; + return } for (let x = 0; x < target.HttpTargets.length; x++) { @@ -176,10 +176,7 @@ function renderHttpTargets(target) { </span> </h3> - <div id="${http.ID}_preview" class="preview mono"> - ${_requestMethods[http.Method]} ${http.Path} <br/> - Content-Type: ${_requestTypes[http.RequestType]} - </div> + <div id="${http.ID}.request" class="request"></div> <h4>Headers</h4> <div id="${http.ID}_headers" class="headers"></div> @@ -201,6 +198,8 @@ function renderHttpTargets(target) { for (let x = 0; x < target.HttpTargets.length; x++) { let http = target.HttpTargets[x] + renderHttpTargetRequest(target, http) + if (Object.keys(http.Headers).length > 0) { renderHttpTargetHeaders(target, http) } @@ -216,10 +215,10 @@ function renderHttpTargets(target) { } function renderWebSocketTargets(target) { - let w = ""; + let w = "" if (!target.WebSocketTargets) { - return; + return } for (let x = 0; x < target.WebSocketTargets.length; x++) { @@ -262,7 +261,6 @@ function renderWebSocketTargets(target) { } } - function renderHttpTargetHeaders(target, http) { let w = "" for (const k in http.Headers) { @@ -279,6 +277,59 @@ function renderHttpTargetHeaders(target, http) { document.getElementById(`${http.ID}_headers`).innerHTML = w } +function renderHttpTargetRequest(target, http) { + let w = ` + <div class="input"> + <label> + <select + name="method" + ${http.IsCustomizable ? "" : "disabled"} + onchange="onChangeRequestMethod(this, '${target.ID}', '${http.ID}')" + > + ` + + for (const m in _requestMethods) { + w += ` + <option value="${m}" ${http.Method == m ? "selected" : ""}> + ${_requestMethods[m]} + </option> + ` + } + + w += ` + </select> + </label> : + <input + value="${http.Path}" + ${http.IsCustomizable ? "" : "readonly"} + onchange="onChangeRequestPath(this, '${target.ID}', '${http.ID}')" + /> + </div> + + <div class="input"> + <label> Content type </label> : + <select + ${http.IsCustomizable ? "" : "disabled"} + onchange="onChangeRequestType(this, '${target.ID}', '${http.ID}')" + > + ` + + for (const ct in _requestTypes) { + w += ` + <option value="${ct}" ${http.RequestType == ct ? "selected" : ""}> + ${_requestTypes[ct]} + </option> + ` + } + + w += ` + </select> + </div> + ` + + document.getElementById(`${http.ID}.request`).innerHTML = w +} + function renderHttpTargetParams(target, http) { let w = "" for (const k in http.Params) { @@ -321,9 +372,16 @@ function renderHttpAttackResults(target, http) { } async function run(targetID, httpTargetID) { + target = _targets[targetID] + let req = {} - req.Target = _targets[targetID] - req.HttpTarget = getHttpTargetByID(req.Target, httpTargetID) + req.Target = { + ID: target.ID, + Opts: target.Opts, + Vars: target.Vars, + } + + req.HttpTarget = getHttpTargetByID(target, httpTargetID) let fres = await fetch("/_trunks/api/target/run/http", { method: "POST", @@ -340,13 +398,31 @@ async function run(targetID, httpTargetID) { } let elResponse = document.getElementById(httpTargetID + "_response") - elResponse.innerHTML = JSON.stringify(res, null, 2) + let m = _requestMethods[req.HttpTarget.Method] + switch (m) { + case "GET": + case "POST": + case "PUT": + case "DELETE": + elResponse.innerHTML = JSON.stringify(res, null, 2) + break + default: + elResponse.innerHTML = atob(res.data) + break + } } async function runWebSocket(targetID, wstID) { + target = _targets[targetID] + let req = {} - req.Target = _targets[targetID] - req.WebSocketTarget = getWebSocketTargetByID(req.Target, wstID) + req.Target = { + ID: target.ID, + Opts: target.Opts, + Vars: target.Vars, + } + + req.WebSocketTarget = getWebSocketTargetByID(target, wstID) let fres = await fetch("/_trunks/api/target/run/websocket", { method: "POST", @@ -517,6 +593,24 @@ function onChangeHttpParam(targetID, httpTargetID, key, val) { httpTarget.Params[key] = val } +function onChangeRequestMethod(el, tid, htid) { + let target = _targets[tid] + let httpTarget = getHttpTargetByID(target, htid) + httpTarget.Method = parseInt(el.value, 10) +} + +function onChangeRequestPath(el, tid, htid) { + let target = _targets[tid] + let httpTarget = getHttpTargetByID(target, htid) + httpTarget.Path = el.value +} + +function onChangeRequestType(el, tid, htid) { + let target = _targets[tid] + let httpTarget = getHttpTargetByID(target, htid) + httpTarget.RequestType = parseInt(el.value, 10) +} + function notif(msg) { let root = document.getElementById("notif") let item = document.createElement("div") diff --git a/example/example.go b/example/example.go index 1d6de3e..ba2dada 100644 --- a/example/example.go +++ b/example/example.go @@ -21,8 +21,7 @@ import ( ) const ( - pathExampleGet = "/example/get" - pathExamplePostForm = "/example/post/form" + pathExample = "/example" ) const ( @@ -101,7 +100,7 @@ func (ex *Example) Stop() { func (ex *Example) registerEndpoints() (err error) { err = ex.trunks.Server.RegisterEndpoint(&libhttp.Endpoint{ Method: libhttp.RequestMethodGet, - Path: pathExampleGet, + Path: pathExample, RequestType: libhttp.RequestTypeQuery, ResponseType: libhttp.ResponseTypeJSON, Call: ex.pathExampleGet, @@ -112,7 +111,7 @@ func (ex *Example) registerEndpoints() (err error) { err = ex.trunks.Server.RegisterEndpoint(&libhttp.Endpoint{ Method: libhttp.RequestMethodPost, - Path: pathExamplePostForm, + Path: pathExample, RequestType: libhttp.RequestTypeForm, ResponseType: libhttp.ResponseTypeJSON, Call: ex.pathExamplePostForm, @@ -122,7 +121,7 @@ func (ex *Example) registerEndpoints() (err error) { } func (ex *Example) registerWebSocketEndpoints() (err error) { - err = ex.wsServer.RegisterTextHandler(http.MethodGet, pathExampleGet, + err = ex.wsServer.RegisterTextHandler(http.MethodGet, pathExample, ex.handleWSExampleGet) if err != nil { return err @@ -144,7 +143,7 @@ func (ex *Example) registerTargets() (err error) { HttpTargets: []*trunks.HttpTarget{{ Name: "HTTP Get", Method: libhttp.RequestMethodGet, - Path: pathExampleGet, + Path: pathExample, RequestType: libhttp.RequestTypeQuery, Headers: trunks.KeyValue{ "X-Get": "1", @@ -159,7 +158,7 @@ func (ex *Example) registerTargets() (err error) { }, { Name: "HTTP Post Form", Method: libhttp.RequestMethodPost, - Path: pathExamplePostForm, + Path: pathExample, RequestType: libhttp.RequestTypeForm, Headers: trunks.KeyValue{ "X-PostForm": "1", @@ -172,6 +171,18 @@ func (ex *Example) registerTargets() (err error) { AllowAttack: true, PreAttack: ex.preattackExamplePostForm, Attack: ex.attackExamplePostForm, + }, { + Name: "HTTP free form", + Method: libhttp.RequestMethodGet, + Path: pathExample, + RequestType: libhttp.RequestTypeForm, + Headers: trunks.KeyValue{ + "X-FreeForm": "1", + }, + Params: trunks.KeyValue{ + "Param1": "123", + }, + IsCustomizable: true, }}, } @@ -207,7 +218,7 @@ func (ex *Example) registerTargets() (err error) { func (ex *Example) pathExampleGet(epr *libhttp.EndpointRequest) ([]byte, error) { res := libhttp.EndpointResponse{} res.Code = http.StatusOK - res.Message = pathExampleGet + res.Message = pathExample res.Data = epr.HttpRequest.Form return json.Marshal(&res) @@ -216,7 +227,7 @@ func (ex *Example) pathExampleGet(epr *libhttp.EndpointRequest) ([]byte, error) func (ex *Example) pathExamplePostForm(epr *libhttp.EndpointRequest) ([]byte, error) { res := libhttp.EndpointResponse{} res.Code = http.StatusOK - res.Message = pathExamplePostForm + res.Message = pathExample res.Data = epr.HttpRequest.Form return json.Marshal(&res) @@ -330,7 +341,7 @@ func (ex *Example) runWebSocketGet(rr *trunks.RunRequest) (resbody []byte, err e req := websocket.Request{ ID: uint64(time.Now().UnixNano()), Method: http.MethodGet, - Target: pathExampleGet, + Target: pathExample, Body: string(body), } diff --git a/http_target.go b/http_target.go index e8ddfb6..f21df13 100644 --- a/http_target.go +++ b/http_target.go @@ -57,6 +57,10 @@ type HttpTarget struct { // interface and client will be allowed to run load testing on this // HttpTarget. AllowAttack bool + + // IsCustomizable allow client to modify the Method, Path, and + // RequestType. + IsCustomizable bool } func (ht *HttpTarget) init() (err error) { diff --git a/key_value.go b/key_value.go index 7147118..91274b6 100644 --- a/key_value.go +++ b/key_value.go @@ -26,6 +26,21 @@ func (kv KeyValue) ToHttpHeader() http.Header { } // +// ToMultipartFormData convert the KeyValue into map of string and raw bytes. +// +func (kv KeyValue) ToMultipartFormData() (data map[string][]byte) { + if kv == nil || len(kv) == 0 { + return nil + } + + data = make(map[string][]byte, len(kv)) + for k, v := range kv { + data[k] = []byte(v) + } + return data +} + +// // ToUrlValues convert the KeyValue to the standard url.Values. // func (kv KeyValue) ToUrlValues() url.Values { @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/http/httputil" "os" "sort" "strings" @@ -404,9 +405,15 @@ func (trunks *Trunks) apiTargetRunHttp(epr *libhttp.EndpointRequest) ([]byte, er return nil, errInvalidHttpTarget(req.HttpTarget.ID) } - req.mergeHttpTarget(trunks.Env, origTarget, origHttpTarget) + if req.HttpTarget.Run != nil { + req.mergeHttpTarget(trunks.Env, origTarget, origHttpTarget) + return req.HttpTarget.Run(req) + } - return req.HttpTarget.Run(req) + req.Target.BaseUrl = origTarget.BaseUrl + req.Target.Name = origTarget.Name + + return trunks.runHttpTarget(req) } func (trunks *Trunks) apiTargetRunWebSocket(epr *libhttp.EndpointRequest) ([]byte, error) { @@ -496,6 +503,101 @@ func (trunks *Trunks) getTargetByResultFilename(name string) (t *Target, ht *Htt return t, ht } +func (trunks *Trunks) runHttpTarget(rr *RunRequest) (resbody []byte, err error) { + var ( + httpRes *http.Response + res = libhttp.EndpointResponse{} + httpc = libhttp.NewClient(rr.Target.BaseUrl, nil, true) + headers = rr.HttpTarget.Headers.ToHttpHeader() + params = rr.HttpTarget.Params.ToUrlValues() + ) + + switch rr.HttpTarget.Method { + case libhttp.RequestMethodGet: + httpRes, resbody, err = httpc.Get(rr.HttpTarget.Path, headers, params) + + case libhttp.RequestMethodConnect, + libhttp.RequestMethodDelete, + libhttp.RequestMethodHead, + libhttp.RequestMethodOptions, + libhttp.RequestMethodPatch, + libhttp.RequestMethodTrace: + + httpReq, err := http.NewRequest(rr.HttpTarget.Method.String(), + fmt.Sprintf("%s%s", rr.Target.BaseUrl, rr.HttpTarget.Path), + nil) + if err != nil { + mlog.Errf("runHttpTarget: %s %s: %s\n", + rr.HttpTarget.Method.String(), + rr.HttpTarget.Path, err) + return nil, errInternal(err) + } + + httpRes, err = httpc.Do(httpReq) + if err != nil { + mlog.Errf("runHttpTarget: %s %s: %s\n", + rr.HttpTarget.Method.String(), + rr.HttpTarget.Path, err) + return nil, errInternal(err) + } + + dumpres, err := httputil.DumpResponse(httpRes, true) + if err != nil { + mlog.Errf("runHttpTarget: %s %s: %s\n", + rr.HttpTarget.Method.String(), + rr.HttpTarget.Path, err) + return nil, errInternal(err) + } + + res.Code = http.StatusOK + res.Data = dumpres + return json.Marshal(res) + + case libhttp.RequestMethodPost: + switch rr.HttpTarget.RequestType { + case libhttp.RequestTypeNone, + libhttp.RequestTypeQuery: + httpRes, resbody, err = httpc.Post( + rr.HttpTarget.Path, headers, params) + + case libhttp.RequestTypeForm: + httpRes, resbody, err = httpc.PostForm( + rr.HttpTarget.Path, headers, params) + + case libhttp.RequestTypeMultipartForm: + httpRes, resbody, err = httpc.PostFormData( + rr.HttpTarget.Path, headers, + rr.HttpTarget.Params.ToMultipartFormData()) + + case libhttp.RequestTypeJSON: + httpRes, resbody, err = httpc.PostJSON( + rr.HttpTarget.Path, headers, + rr.HttpTarget.Params) + } + + case libhttp.RequestMethodPut: + if rr.HttpTarget.RequestType == libhttp.RequestTypeJSON { + httpRes, resbody, err = httpc.PutJSON( + rr.HttpTarget.Path, headers, + rr.HttpTarget.Params) + } else { + httpRes, resbody, err = httpc.Put( + rr.HttpTarget.Path, headers, nil) + } + } + if err != nil { + return nil, errInternal(err) + } + if httpRes.StatusCode != http.StatusOK { + res.Code = httpRes.StatusCode + res.Message = httpRes.Status + res.Data = resbody + return json.Marshal(res) + } + + return resbody, err +} + // // scanResultsDir scan the environment's ResultsDir for the past attack // results and add it to each target based on ID on file name. |
