10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Drake/Components/Button.razor:
--------------------------------------------------------------------------------
1 |
4 |
5 | @code {
6 | bool executing = false;
7 |
8 | [Parameter]
9 | public RenderFragment ChildContent { get; set; }
10 |
11 | [Parameter]
12 | public EventCallback OnClick { get; set; }
13 |
14 | async Task ExecuteClick()
15 | {
16 | executing = true;
17 | StateHasChanged();
18 | await OnClick.InvokeAsync();
19 | executing = false;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/Drake/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components.Web;
2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
3 | using Drake;
4 | using UglyDuckling;
5 |
6 | var builder = WebAssemblyHostBuilder.CreateDefault(args);
7 | builder.RootComponents.Add("#app");
8 | builder.RootComponents.Add("head::after");
9 | builder.Services.AddTransient();
10 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
11 | builder.Services.AddHttpClient();
12 |
13 |
14 |
15 |
16 | await builder.Build().RunAsync();
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Drake/Drake.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Drake.Tests/Drake.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 | all
17 |
18 |
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 | all
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Jimmy Engstrom
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Drake/Pages/Index.razor:
--------------------------------------------------------------------------------
1 | @page "/"
2 | @inject NavigationManager manager
3 |
4 |
5 |
6 |
7 |
8 |
Prerequisities
9 | Ugly duckling is running completely inside the web browser.
10 | To be able to make calls to other websites CORS needs to be temporarily disabled.
11 |
12 |
Microsoft Edge
13 | Install the following extension.
14 | Get Extension
15 |
16 |
Google Chrome
17 | Install the following extension.
18 | Get Extension
19 |
20 |
21 |
22 | @code {
23 | void NavigateToDuckling()
24 | {
25 | manager.NavigateTo("EditModule");
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Drake/wwwroot/css/drake.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --accent-color: #ffbc00;
3 | --button-color: #ffbc00;
4 | --text-color: white;
5 | --button-text-color: #252734;
6 | --link-text-color: var(--accent-color);
7 | --background-color: #333646;
8 | }
9 |
10 | * {
11 | padding: 0;
12 | margin: 0;
13 | }
14 |
15 |
16 | body {
17 | font-size: 1.2rem;
18 | line-height: 1.7rem;
19 | font-family: 'Averta Standard', sans-serif;
20 | color: var(--text-color);
21 | background-color: var(--background-color);
22 | padding:20px
23 | }
24 |
25 | main {
26 | display: grid;
27 | grid-template-columns: [Main] 5fr [Side] 2fr;
28 | grid-auto-columns: 1fr;
29 | gap: 0;
30 | grid-auto-flow: row;
31 | }
32 |
33 | .mainarea {
34 | grid-area: Main;
35 |
36 | }
37 | .sidearea {
38 | grid-area: Side;
39 | padding-left:20px;
40 | }
41 |
42 |
43 | .failed {
44 | content: "\2713";
45 | color:red;
46 | }
47 |
48 | .success {
49 | content: "\2717";
50 | color:green;
51 | }
52 |
53 | li{
54 | list-style-type:none;
55 | }
56 |
57 | a {
58 | color: var(--link-text-color);
59 | }
60 |
61 | h2,h3{
62 | margin-top:7px;
63 | margin-bottom:2px;
64 | }
65 |
--------------------------------------------------------------------------------
/Drake/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "iisExpress": {
4 | "applicationUrl": "http://localhost:32864",
5 | "sslPort": 44312
6 | }
7 | },
8 | "profiles": {
9 | "http": {
10 | "commandName": "Project",
11 | "dotnetRunMessages": true,
12 | "launchBrowser": true,
13 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
14 | "applicationUrl": "http://localhost:5237",
15 | "environmentVariables": {
16 | "ASPNETCORE_ENVIRONMENT": "Development"
17 | }
18 | },
19 | "https": {
20 | "commandName": "Project",
21 | "dotnetRunMessages": true,
22 | "launchBrowser": true,
23 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
24 | "applicationUrl": "https://localhost:7124;http://localhost:5237",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | },
29 | "IIS Express": {
30 | "commandName": "IISExpress",
31 | "launchBrowser": true,
32 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
33 | "environmentVariables": {
34 | "ASPNETCORE_ENVIRONMENT": "Development"
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Drake.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.33103.201
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Drake", "Drake\Drake.csproj", "{7C812D56-13C8-4D09-B265-737554FDD603}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drake.Tests", "Drake.Tests\Drake.Tests.csproj", "{9195EF68-2F50-4A57-88AF-3548A61760E8}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {7C812D56-13C8-4D09-B265-737554FDD603}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {7C812D56-13C8-4D09-B265-737554FDD603}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {7C812D56-13C8-4D09-B265-737554FDD603}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {7C812D56-13C8-4D09-B265-737554FDD603}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {9195EF68-2F50-4A57-88AF-3548A61760E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {9195EF68-2F50-4A57-88AF-3548A61760E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {9195EF68-2F50-4A57-88AF-3548A61760E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {9195EF68-2F50-4A57-88AF-3548A61760E8}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {E719864B-D9FB-4F77-BF6C-ACBC6E07EA3C}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/Drake/UglyDuckling/Module.cs:
--------------------------------------------------------------------------------
1 | // Root myDeserializedClass = JsonSerializer.Deserialize(myJsonResponse);
2 | using System.Text.Json.Serialization;
3 |
4 | public class Match
5 | {
6 | [JsonPropertyName("type")]
7 | public string Type { get; set; }
8 |
9 | [JsonPropertyName("pattern")]
10 | public string Pattern { get; set; }
11 |
12 | [JsonPropertyName("required")]
13 | public bool? Required { get; set; }
14 |
15 | [JsonPropertyName("code")]
16 | public int? Code { get; set; }
17 |
18 | [JsonPropertyName("name")]
19 | public string Name { get; set; }
20 | }
21 |
22 |
23 | public class Request
24 | {
25 | [JsonPropertyName("method")]
26 | public string Method { get; set; }
27 | [JsonPropertyName("path")]
28 | public string Path { get; set; }
29 |
30 | [JsonPropertyName("paths")]
31 | public List Paths { get; set; } = new();
32 |
33 | [JsonPropertyName("body")]
34 | public string Body { get; set; }
35 |
36 | [JsonPropertyName("headers")]
37 | public List Headers { get; set; } = new();
38 | }
39 |
40 | public class Response
41 | {
42 | [JsonPropertyName("matchesRequired")]
43 | public int MatchesRequired { get; set; } = 1;
44 |
45 | [JsonPropertyName("matches")]
46 | public List Matches { get; set; } = new();
47 |
48 | [JsonPropertyName("mustNotMatch")]
49 | public List MustNotMatch { get; set; } = new();
50 | }
51 |
52 | public class Module
53 | {
54 | [JsonPropertyName("name")]
55 | public string Name { get; set; }
56 |
57 | [JsonPropertyName("request")]
58 | public Request Request { get; set; }
59 |
60 | [JsonPropertyName("response")]
61 | public Response Response { get; set; }
62 | }
63 |
64 | ///https://cveawg.mitre.org/api/cve/CVE-2017-9140
65 | ///https://nvd.nist.gov/vuln/detail/CVE-2017-9140
--------------------------------------------------------------------------------
/Drake/wwwroot/css/app.css:
--------------------------------------------------------------------------------
1 | h1:focus {
2 | outline: none;
3 | }
4 |
5 | #blazor-error-ui {
6 | background: lightyellow;
7 | bottom: 0;
8 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
9 | display: none;
10 | left: 0;
11 | padding: 0.6rem 1.25rem 0.7rem 1.25rem;
12 | position: fixed;
13 | width: 100%;
14 | z-index: 1000;
15 | }
16 |
17 | #blazor-error-ui .dismiss {
18 | cursor: pointer;
19 | position: absolute;
20 | right: 0.75rem;
21 | top: 0.5rem;
22 | }
23 |
24 | .blazor-error-boundary {
25 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
26 | padding: 1rem 1rem 1rem 3.7rem;
27 | color: white;
28 | }
29 |
30 | .blazor-error-boundary::after {
31 | content: "An error has occurred."
32 | }
33 |
--------------------------------------------------------------------------------
/Drake/UglyDuckling/UglyDucklingEngine.cs:
--------------------------------------------------------------------------------
1 | using Drake.UglyDuckling;
2 | using System.Net.Http.Headers;
3 |
4 | namespace UglyDuckling;
5 | public class UglyDucklingEngine
6 | {
7 | public async Task ValidateRequestAsync(Module module, string responseBody, int responseStatusCode, HttpResponseHeaders headers)
8 | {
9 | var result=new ModuleResult();
10 |
11 | //Verify module
12 | if (module.Response.Matches != null)
13 | {
14 | foreach (var match in module.Response.Matches)
15 | {
16 | var mresult=await HandleMatchAsync(match,responseStatusCode, responseBody,headers);
17 | //If match is required but not matched
18 | if (!mresult.IsMatched && (match.Required??false))
19 | {
20 | result.VulnerabilityFound = false;
21 | }
22 | result.ShouldMatch.Add(mresult);
23 | }
24 | }
25 | if (module.Response.MustNotMatch != null)
26 | {
27 | foreach (var match in module.Response.MustNotMatch)
28 | {
29 | var mresult = await HandleMatchAsync(match, responseStatusCode, responseBody,headers);
30 |
31 | if (mresult.IsMatched)
32 | {
33 | result.VulnerabilityFound = false;
34 | }
35 | result.ShouldNotMatch.Add(mresult);
36 | }
37 | }
38 | if (result.VulnerabilityFound == null)
39 | {
40 | result.VulnerabilityFound = result.ShouldMatch.Count(m => m.IsMatched) >= module.Response.MatchesRequired;
41 | }
42 | return result;
43 | }
44 |
45 | async Task HandleMatchAsync(Match match, int statusCode, string responseBody, HttpResponseHeaders headers)
46 | {
47 | var isMatch = false;
48 | if (match.Type == "static")
49 | {
50 | if (responseBody.Contains(match.Pattern))
51 | {
52 | isMatch = true;
53 | }
54 | }
55 | if (match.Type == "regex")
56 | {
57 | System.Text.RegularExpressions.Regex regex = new(match.Pattern);
58 | if (regex.IsMatch(responseBody))
59 | {
60 | isMatch = true;
61 | }
62 | }
63 |
64 | if (match.Type == "status" && match.Code != null)
65 | {
66 | if (statusCode == match.Code)
67 | {
68 | isMatch = true;
69 | }
70 | }
71 |
72 | if (match.Type == "header")
73 | {
74 | if (headers.TryGetValues(match.Name, out var values))
75 | {
76 | foreach (var v in values)
77 | {
78 | System.Text.RegularExpressions.Regex regex = new(match.Pattern);
79 | if (regex.IsMatch(v))
80 | {
81 | isMatch = true;
82 | }
83 | }
84 | }
85 | }
86 |
87 | return new() { Match = match, IsMatched = isMatch};
88 | }
89 |
90 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Drake
2 | ## The name
3 | This is a web interface for Ugly Duckling, Detectifys module test application.
4 | I wanted a name that was a synonym with duck and found "Drake".
5 | I then realized that Drake Mallard (the alter-ego of Darkwing Duck) is actually called Drake for that reason =D.
6 | It just happens to be that "Drake" also means dragon in Swedish so the name was perfect.
7 |
8 |
9 | ## What is it?
10 | A web interface for Ugly Duckling.
11 | My wife started her new role as the Community Manager for Detectify.
12 | To understand more of what she was doing (since I don't have a background in cyber security) I decided to learn more by writing some code.
13 | Detectify launched a new tool called Ugly Ducking where ethical hackers can build and test their modules before submitting them.
14 | The format is a JSON format that describes the request and what to test in the response to identify a possible threat.
15 | Read more here https://labs.detectify.com/2021/05/18/detectify-research-team-releases-ugly-duckling-a-web-scanner-for-ethical-hackers/
16 | To make this run I would have to install Go and learn how that works (which is something I wasn't too keen to do).
17 | So I thought would it be possible to make a Blazor implementation of this?
18 |
19 | Since Microsoft has released a version of Monaco (the engine VSCode is running) I wanted to give that a try.
20 | I also added a JSON schema that can be used in the web implementation or directly from VSCode to get full intelisence when editing modules.
21 | The goal of the project is to make it easier to write and test modules for Ugly Duckling.
22 | ## Usage
23 | There are two ways to use this application.
24 | 1. We can use the online version at https://engstromjimmy.github.io/Drake with an version of Monaco (VSCode).
25 | This is the easiest way to get started.
26 | Since it is JavaScript making the calls we need to install a plugin in the Web browser to be able to make cross-site calls.
27 | But if you want to test it without installing anything you can test the default module with the URL "https://engstromjimmy.github.io/Drake/".
28 |
29 | 2. We can use VSCode directly by simply refering to the JSON Schema.
30 | ```
31 | {
32 | "$schema": "https://raw.githubusercontent.com/EngstromJimmy/Drake/main/ModuleSchema.json",
33 | "response": {
34 | "matches": [
35 | {
36 | "type": "status",
37 | "code": 200
38 | },
39 | {
40 | "name": "Hello World",
41 | "type":"regex"
42 | }
43 | ]
44 | }
45 | }
46 | ```
47 |
--------------------------------------------------------------------------------
/.github/workflows/static.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Publish Drake
4 | env:
5 | PUBLISH_DIR: Drake/bin/Release/net7.0/publish/wwwroot
6 |
7 | # Controls when the workflow will run
8 | on:
9 | # Triggers the workflow on push or pull request events but only for the master branch
10 | push:
11 | branches: [ main ]
12 | pull_request:
13 | branches: [ main ]
14 |
15 | # Allows you to run this workflow manually from the Actions tab
16 | workflow_dispatch:
17 |
18 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
19 | jobs:
20 | # This workflow contains a single job called "build"
21 | build:
22 | # The type of runner that the job will run on
23 | runs-on: windows-2022
24 |
25 | # Steps represent a sequence of tasks that will be executed as part of the job
26 | steps:
27 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
28 | - uses: actions/checkout@v2
29 |
30 | - uses: actions/setup-dotnet@v1
31 | with:
32 | dotnet-version: '7.0.x'
33 | include-prerelease: true
34 |
35 | #- name: Install tools
36 | # run: dotnet workload install wasm-tools
37 |
38 | - name: Publish application
39 | run: dotnet publish -c Release Drake/Drake.csproj
40 |
41 |
42 | - name: Change base-tag in index.html from / to BlazorGitHubPagesDemo
43 | run: (Get-content -path '${{ env.PUBLISH_DIR }}/index.html') -replace '', '' | Out-File -FilePath '${{ env.PUBLISH_DIR }}/index.html'
44 |
45 | # add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project.
46 | # Allow files and folders starting with an underscore.
47 | - name: Add .nojekyll file
48 | run: touch ${{ env.PUBLISH_DIR }}/.nojekyll
49 |
50 | # copy index.html to 404.html to serve the same file when a file is not found this makes deep linking possible
51 | - name: copy index.html to 404.html
52 | run: cp ${{ env.PUBLISH_DIR }}/index.html ${{ env.PUBLISH_DIR }}/404.html
53 |
54 | #Add .giattributes file so all files are handled as binary
55 | - name: Add .giattributes
56 | run: echo "* binary" > "${{ env.PUBLISH_DIR }}/.gitattributes"
57 |
58 | - name: Commit to GitHub pages Repo
59 | if: success()
60 | uses: crazy-max/ghaction-github-pages@v1.5.1
61 | with:
62 | target_branch: gh-pages
63 | build_dir: ${{ env.PUBLISH_DIR }}
64 | env:
65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66 |
--------------------------------------------------------------------------------
/ModuleSchema.json:
--------------------------------------------------------------------------------
1 | {
2 | "uri": "http://detectify/module.json",
3 | "additionalProperties": true,
4 | "definitions": {
5 | "request": {
6 | "type": "object",
7 | "properties": {
8 | "method": { "enum": [ "GET", "POST", "HEAD", "PUT", "OPTIONS", "PATCH", "DELETE" ] },
9 | "path": { "type": "string" },
10 | "paths": { "type": "array" },
11 | "body": { "type": "object" },
12 | "headers": { "type": "array" }
13 | }
14 | },
15 | "response": {
16 | "type": "object",
17 | "properties": {
18 | "matchesRequired": { "type": "integer" },
19 | "matches": {
20 | "type": "array",
21 | "items": {
22 | "$ref": "#/definitions/match"
23 | }
24 | },
25 | "mustNotMatch": {
26 | "type": "array",
27 | "items": {
28 | "$ref": "#/definitions/match"
29 | }
30 | }
31 | }
32 | },
33 | "match": {
34 | "type": "object",
35 | "properties": {
36 | "type": {
37 | "type": "string",
38 | "enum": [ "static", "regex", "status", "header" ]
39 | },
40 | "pattern": { "type": "string" },
41 | "status": { "type": "integer" },
42 | "name": { "type": "string" },
43 | "code": { "type": "integer" }
44 | },
45 | "oneOf":
46 | [
47 | {
48 | "properties": {
49 | "type": {
50 | "type": "string",
51 | "enum": [ "static" ]
52 | },
53 | "pattern": { "type": "string" }
54 | },
55 | "required": [ "type", "pattern" ],
56 | "additionalProperties": false
57 | },
58 | {
59 | "properties": {
60 | "type": {
61 | "type": "string",
62 | "enum": [ "status" ]
63 | },
64 | "code": { "type": "integer" }
65 | },
66 | "required": [ "type", "code" ],
67 | "additionalProperties": false
68 | },
69 | {
70 | "properties": {
71 | "type": {
72 | "type": "string",
73 | "enum": [ "header" ]
74 | },
75 | "name": { "type": "string" },
76 | "pattern": { "type": "string" }
77 |
78 | },
79 | "required": [ "type", "name", "pattern" ],
80 | "additionalProperties": false
81 | },
82 | {
83 | "properties": {
84 | "type": {
85 | "type": "string",
86 | "enum": [ "regex" ]
87 | },
88 | "pattern": { "type": "string" }
89 |
90 | },
91 | "required": [ "type", "pattern" ],
92 | "additionalProperties": false
93 | }
94 | ]
95 | }
96 | },
97 | "properties": {
98 | "submitter": { "type": "string" },
99 | "request": {
100 | "$ref": "#/definitions/request"
101 | },
102 | "response": {
103 | "$ref": "#/definitions/response"
104 | }
105 |
106 | },
107 | "required": [ "response", "request" ]
108 | }
109 |
--------------------------------------------------------------------------------
/Drake.Tests/UglyDucklingEngineTests.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Headers;
2 | using UglyDuckling;
3 |
4 | namespace Drake.Tests;
5 |
6 | public class UglyDucklingEngineTests
7 | {
8 | [Fact]
9 | public async Task StaticShouldMatch()
10 | {
11 | UglyDucklingEngine engine = new();
12 | Module m = new();
13 | m.Response = new();
14 | m.Response.Matches.Add(new Match() { Type = "static", Pattern = "Test" });
15 | var message = new HttpResponseMessage();
16 | var headers = message.Headers;
17 |
18 | var result= await engine.ValidateRequestAsync(m, "Test", 200, headers);
19 | Assert.NotNull(result);
20 | Assert.True(result.VulnerabilityFound);
21 | }
22 |
23 | [Fact]
24 | public async Task StaticShouldNotMatch()
25 | {
26 | UglyDucklingEngine engine = new();
27 | Module m = new();
28 | m.Response = new();
29 | m.Response.Matches.Add(new Match() { Type = "static", Pattern = "NotMatching" });
30 | var message = new HttpResponseMessage();
31 | var headers = message.Headers;
32 |
33 | var result = await engine.ValidateRequestAsync(m, "Test", 200, headers);
34 | Assert.NotNull(result);
35 | Assert.False(result.VulnerabilityFound);
36 | }
37 | [Fact]
38 | public async Task StatusCodeShouldMatch()
39 | {
40 | UglyDucklingEngine engine = new();
41 | Module m = new();
42 | m.Response = new();
43 | m.Response.Matches.Add(new Match() { Type = "status", Code=200 });
44 | var message = new HttpResponseMessage();
45 | var headers = message.Headers;
46 |
47 | var result = await engine.ValidateRequestAsync(m, "Test", 200, headers);
48 | Assert.NotNull(result);
49 | Assert.True(result.VulnerabilityFound);
50 | }
51 | [Fact]
52 | public async Task StatusCodeShouldNotMatch()
53 | {
54 | UglyDucklingEngine engine = new();
55 | Module m = new();
56 | m.Response = new();
57 | m.Response.Matches.Add(new Match() { Type = "status", Code = 1337 });
58 | var message = new HttpResponseMessage();
59 | var headers = message.Headers;
60 |
61 | var result = await engine.ValidateRequestAsync(m, "Test", 200, headers);
62 | Assert.NotNull(result);
63 | Assert.False(result.VulnerabilityFound);
64 | }
65 |
66 |
67 | [Fact]
68 | public async Task RegexShouldMatch()
69 | {
70 | UglyDucklingEngine engine = new();
71 | Module m = new();
72 | m.Response = new();
73 | m.Response.Matches.Add(new Match() { Type = "regex", Pattern= "[1][1-3][1-3][7]" });
74 | var message = new HttpResponseMessage();
75 | var headers = message.Headers;
76 |
77 | var result = await engine.ValidateRequestAsync(m, "1337", 200, headers);
78 | Assert.NotNull(result);
79 | Assert.True(result.VulnerabilityFound);
80 | }
81 |
82 | [Fact]
83 | public async Task RegexShouldNotMatch()
84 | {
85 | UglyDucklingEngine engine = new();
86 | Module m = new();
87 | m.Response = new();
88 | m.Response.Matches.Add(new Match() { Type = "regex", Pattern = "[1][1-3][1-3][7]" });
89 | var message = new HttpResponseMessage();
90 | var headers = message.Headers;
91 |
92 | var result = await engine.ValidateRequestAsync(m, "1338", 200, headers);
93 | Assert.NotNull(result);
94 | Assert.False(result.VulnerabilityFound);
95 | }
96 |
97 | [Fact]
98 | public async Task HeaderShouldNotMatch()
99 | {
100 | UglyDucklingEngine engine = new();
101 | Module m = new();
102 | m.Response = new();
103 | m.Response.Matches.Add(new Match() { Type = "header", Name= "HeaderKey", Pattern = "[1][1-3][1-3][7]" });
104 | var message = new HttpResponseMessage();
105 | var headers = message.Headers;
106 | headers.Add("HeaderKey", "1338");
107 | var result = await engine.ValidateRequestAsync(m, "1338", 200, headers);
108 | Assert.NotNull(result);
109 | Assert.False(result.VulnerabilityFound);
110 | }
111 |
112 | [Fact]
113 | public async Task HeaderShouldMatch()
114 | {
115 | UglyDucklingEngine engine = new();
116 | Module m = new();
117 | m.Response = new();
118 | m.Response.Matches.Add(new Match() { Type = "header", Name = "HeaderKey", Pattern = "[1][1-3][1-3][7]" });
119 | var message = new HttpResponseMessage();
120 | var headers = message.Headers;
121 | headers.Add("HeaderKey", "Other1337Text");
122 | var result = await engine.ValidateRequestAsync(m, "1338", 200, headers);
123 | Assert.NotNull(result);
124 | Assert.True(result.VulnerabilityFound);
125 | }
126 |
127 | }
--------------------------------------------------------------------------------
/Drake/wwwroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Drake
7 |
8 |
9 |
10 |
11 |
12 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | An unhandled error has occurred.
160 | Reload
161 | 🗙
162 |