From 73342b1ecb4c18b139514cde2cc1761a03ed7705 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sat, 16 Mar 2024 00:55:11 +0900 Subject: [PATCH 01/29] Update Crystal versions in CI workflow and shard.yml --- .github/workflows/ci.yml | 2 +- shard.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cda9a67f..1ddaae5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - crystal-version: ['1.8.0', '1.9.0', '1.10.0', '1.11.0'] + crystal-version: ['1.10.0', '1.11.0'] steps: - uses: actions/checkout@v3 - uses: MeilCli/setup-crystal-action@v4 diff --git a/shard.yml b/shard.yml index e72751c7..ab84098a 100644 --- a/shard.yml +++ b/shard.yml @@ -13,6 +13,6 @@ dependencies: crest: github: mamantoha/crest -crystal: 1.8.2 +crystal: ~> 1.10 license: MIT From af1fd8e3ce9c57c1430a86340cfa485d357edfd9 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sat, 16 Mar 2024 01:07:10 +0900 Subject: [PATCH 02/29] Update project description in snapcraft.yaml --- snap/snapcraft.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 90ec266b..37e25ad7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -3,8 +3,8 @@ base: core20 version: 0.13.0 summary: Attack surface detector that identifies endpoints by static analysis. description: | - Noir is your ally in the quest for digital fortification. - A cutting-edge attack surface detector, it unveils hidden endpoints through meticulous static analysis. + Noir is an open-source project specializing in identifying attack surfaces for enhanced whitebox security testing and security pipeline. + This includes the capability to discover API endpoints, web endpoints, and other potential entry points within source code for thorough security analysis. grade: stable # must be 'stable' to release into candidate/stable channels confinement: strict # use 'strict' once you have the right plugs and slots From 022edee48032e6321580ec558934946c816a2229 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sun, 17 Mar 2024 01:29:12 +0900 Subject: [PATCH 03/29] =?UTF-8?q?=F0=9F=9A=80=20Add=20HAR=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + shard.lock | 4 + shard.yml | 2 + spec/functional_test/fixtures/har/example.har | 153 ++++++++++++++++++ spec/functional_test/func_spec.cr | 4 + spec/functional_test/testers/har_spec.cr | 19 +++ src/analyzer/analyzer.cr | 1 + src/analyzer/analyzers/analyzer_har.cr | 68 ++++++++ src/detector/detector.cr | 1 + src/detector/detectors/har.cr | 29 ++++ src/techs/techs.cr | 9 ++ 11 files changed, 291 insertions(+) create mode 100644 spec/functional_test/fixtures/har/example.har create mode 100644 spec/functional_test/testers/har_spec.cr create mode 100644 src/analyzer/analyzers/analyzer_har.cr create mode 100644 src/detector/detectors/har.cr diff --git a/README.md b/README.md index 3d60773f..3d5ab1b6 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ | OAS 3.0 | JSON | ✅ | ✅ | ✅ | ✅ | X | | OAS 3.0 | YAML | ✅ | ✅ | ✅ | ✅ | X | | RAML | YAML | ✅ | ✅ | ✅ | ✅ | X | +| HAR | JSON | ✅ | ✅ | ✅ | ✅ | X | ## Installation ### Homebrew (macOS) diff --git a/shard.lock b/shard.lock index 3211fdbc..bdf4e9a2 100644 --- a/shard.lock +++ b/shard.lock @@ -4,6 +4,10 @@ shards: git: https://github.com/mamantoha/crest.git version: 1.3.11 + har: + git: https://github.com/neuralegion/har.git + version: 1.2.0 + http-client-digest_auth: git: https://github.com/mamantoha/http-client-digest_auth.git version: 0.6.0 diff --git a/shard.yml b/shard.yml index e72751c7..0f43a29d 100644 --- a/shard.yml +++ b/shard.yml @@ -12,6 +12,8 @@ targets: dependencies: crest: github: mamantoha/crest + har: + github: NeuraLegion/har crystal: 1.8.2 diff --git a/spec/functional_test/fixtures/har/example.har b/spec/functional_test/fixtures/har/example.har new file mode 100644 index 00000000..89e6d01f --- /dev/null +++ b/spec/functional_test/fixtures/har/example.har @@ -0,0 +1,153 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Firefox", + "version": "123.0.1" + }, + "browser": { + "name": "Firefox", + "version": "123.0.1" + }, + "pages": [ + { + "id": "page_1", + "pageTimings": { + "onContentLoad": -1, + "onLoad": -1 + }, + "startedDateTime": "2024-03-17T00:17:31.653+09:00", + "title": "https://www.hahwul.com/" + } + ], + "entries": [ + { + "startedDateTime": "2024-03-17T00:17:31.653+09:00", + "request": { + "bodySize": 0, + "method": "GET", + "url": "https://www.hahwul.com/", + "httpVersion": "HTTP/2", + "headers": [ + { + "name": "Host", + "value": "www.hahwul.com" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0" + }, + { + "name": "Accept", + "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8" + } + ], + "cookies": [ + { + "name": "_ga", + "value": "GA1.1.1310623768.1671977578" + }, + { + "name": "_ga_N9SYSZ280B", + "value": "GS1.1.1710602187.53.0.1710602187.0.0.0" + } + ], + "queryString": [], + "headersSize": 690 + }, + "response": { + "status": 304, + "statusText": "", + "httpVersion": "HTTP/2", + "headers": [ + { + "name": "date", + "value": "Sat, 16 Mar 2024 15:17:31 GMT" + }, + { + "name": "via", + "value": "1.1 varnish" + }, + { + "name": "cache-control", + "value": "max-age=600" + }, + { + "name": "etag", + "value": "W/\"65f5937e-aadc\"" + }, + { + "name": "expires", + "value": "Sat, 16 Mar 2024 13:59:28 GMT" + }, + { + "name": "age", + "value": "258" + }, + { + "name": "x-served-by", + "value": "cache-icn1450027-ICN" + }, + { + "name": "x-cache", + "value": "HIT" + }, + { + "name": "x-cache-hits", + "value": "3" + }, + { + "name": "x-timer", + "value": "S1710602252.651544,VS0,VE1" + }, + { + "name": "vary", + "value": "Accept-Encoding" + }, + { + "name": "x-fastly-request-id", + "value": "f40aba25bfeacafe5eb1bce11729a353344a30c3" + }, + { + "name": "X-Firefox-Spdy", + "value": "h2" + } + ], + "cookies": [], + "content": { + "mimeType": "text/plain", + "size": 0, + "text": "\n\n......\n\n\n" + }, + "redirectURL": "", + "headersSize": 382, + "bodySize": 382 + }, + "cache": { + "afterRequest": { + "expires": "1709911195", + "lastFetched": "1710602189", + "fetchCount": "2", + "_dataSize": "9437", + "_lastModified": "1710602191", + "_device": "" + } + }, + "timings": { + "blocked": 0, + "dns": 0, + "connect": 0, + "ssl": 0, + "send": 0, + "wait": 7, + "receive": 0 + }, + "time": 7, + "_securityState": "secure", + "serverIPAddress": "185.199.108.153", + "connection": "443", + "pageref": "page_1" + } + ] + } +} \ No newline at end of file diff --git a/spec/functional_test/func_spec.cr b/spec/functional_test/func_spec.cr index b5b13a73..a912e8fe 100644 --- a/spec/functional_test/func_spec.cr +++ b/spec/functional_test/func_spec.cr @@ -128,4 +128,8 @@ class FunctionalTester def app @app end + + def set_url(url) + @app.options[:url] = url + end end diff --git a/spec/functional_test/testers/har_spec.cr b/spec/functional_test/testers/har_spec.cr new file mode 100644 index 00000000..2e71940e --- /dev/null +++ b/spec/functional_test/testers/har_spec.cr @@ -0,0 +1,19 @@ +require "../func_spec.cr" + +extected_endpoints = [ + Endpoint.new("https://www.hahwul.com/", "GET", [ + Param.new("Host", "www.hahwul.com", "header"), + Param.new("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0", "header"), + Param.new("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "header"), + Param.new("_ga", "GA1.1.1310623768.1671977578", "cookie"), + Param.new("_ga_N9SYSZ280B", "GS1.1.1710602187.53.0.1710602187.0.0.0", "cookie"), + ]), +] + +instance = FunctionalTester.new("fixtures/har/", { + :techs => 1, + :endpoints => 1, +}, extected_endpoints) + +instance.set_url "https://www.hahwul.com" +instance.test_all diff --git a/src/analyzer/analyzer.cr b/src/analyzer/analyzer.cr index 09a82483..d6b09e9f 100644 --- a/src/analyzer/analyzer.cr +++ b/src/analyzer/analyzer.cr @@ -13,6 +13,7 @@ def initialize_analyzers(logger : NoirLogger) analyzers["go_echo"] = ->analyzer_go_echo(Hash(Symbol, String)) analyzers["go_fiber"] = ->analyzer_go_fiber(Hash(Symbol, String)) analyzers["go_gin"] = ->analyzer_go_gin(Hash(Symbol, String)) + analyzers["har"] = ->analyzer_har(Hash(Symbol, String)) analyzers["java_armeria"] = ->analyzer_armeria(Hash(Symbol, String)) analyzers["java_jsp"] = ->analyzer_jsp(Hash(Symbol, String)) analyzers["java_spring"] = ->analyzer_java_spring(Hash(Symbol, String)) diff --git a/src/analyzer/analyzers/analyzer_har.cr b/src/analyzer/analyzers/analyzer_har.cr new file mode 100644 index 00000000..b4cf32a1 --- /dev/null +++ b/src/analyzer/analyzers/analyzer_har.cr @@ -0,0 +1,68 @@ +require "../../models/analyzer" + +class AnalyzerHar < Analyzer + def analyze + locator = CodeLocator.instance + har_files = locator.all("har-path") + + if har_files.is_a?(Array(String)) && @url != "" + har_files.each do |har_file| + if File.exists?(har_file) + data = HAR.from_file(har_file) + logger.debug "Open #{har_file} file" + data.entries.each do |entry| + if entry.request.url.includes? @url + path = entry.request.url.to_s.gsub(@url, "") + endpoint = Endpoint.new(path, entry.request.method) + + entry.request.query_string.each do |query| + endpoint.params << Param.new(query.name, query.value, "query") + end + + is_websocket = false + entry.request.headers.each do |header| + endpoint.params << Param.new(header.name, header.value, "header") + if header.name == "Upgrade" && header.value == "websocket" + is_websocket = true + end + end + + entry.request.cookies.each do |cookie| + endpoint.params << Param.new(cookie.name, cookie.value, "cookie") + end + + post_data = entry.request.post_data + if post_data + params = post_data.params + mime_type = post_data.mime_type + param_type = "body" + if mime_type == "application/json" + param_type = "json" + end + if params + params.each do |param| + endpoint.params << Param.new(param.name, param.value.to_s, param_type) + end + end + end + + details = Details.new(PathInfo.new(har_file, 0)) + endpoint.set_details(details) + if is_websocket + endpoint.set_protocol "ws" + end + @result << endpoint + end + end + end + end + end + + @result + end +end + +def analyzer_har(options : Hash(Symbol, String)) + instance = AnalyzerHar.new(options) + instance.analyze +end diff --git a/src/detector/detector.cr b/src/detector/detector.cr index 33266ebd..37bcf295 100644 --- a/src/detector/detector.cr +++ b/src/detector/detector.cr @@ -22,6 +22,7 @@ def detect_techs(base_path : String, options : Hash(Symbol, String), logger : No DetectorGoEcho, DetectorGoFiber, DetectorGoGin, + DetectorHar, DetectorJavaArmeria, DetectorJavaJsp, DetectorJavaSpring, diff --git a/src/detector/detectors/har.cr b/src/detector/detectors/har.cr new file mode 100644 index 00000000..44cc53ae --- /dev/null +++ b/src/detector/detectors/har.cr @@ -0,0 +1,29 @@ +require "../../models/detector" +require "../../utils/json" +require "../../utils/yaml" +require "../../models/code_locator" +require "har" + +class DetectorHar < Detector + def detect(filename : String, file_contents : String) : Bool + if (filename.includes? ".har") || (filename.includes? ".json") + if valid_json? file_contents + begin + data = HAR.from_string(file_contents) + if data.version.to_s.includes? "1." + locator = CodeLocator.instance + locator.push("har-path", filename) + return true + end + rescue + end + end + end + + false + end + + def set_name + @name = "har" + end +end diff --git a/src/techs/techs.cr b/src/techs/techs.cr index ed1bdf5b..ea543b85 100644 --- a/src/techs/techs.cr +++ b/src/techs/techs.cr @@ -5,6 +5,11 @@ module NoirTechs :language => "Crystal", :similar => ["kemal", "crystal-kemal", "crystal_kemal"], }, + :crystal_lucky => { + :framework => "Lucky", + :language => "Crystal", + :similar => ["lucky", "crystal-lucky", "crystal_lucky"], + }, :cs_aspnet_mvc => { :framework => "ASP.NET MVC", :language => "C#", @@ -30,6 +35,10 @@ module NoirTechs :language => "Go", :similar => ["gin", "go-gin", "go_gin"], }, + :har => { + :format => ["JSON"], + :similar => ["har"], + }, :java_armeria => { :framework => "Armeria", :language => "Java", From 8cb29932594f4ef252861ca0c4b26e0cb91e2379 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sun, 17 Mar 2024 17:29:16 +0900 Subject: [PATCH 04/29] Add public dir process in crystal-kemal --- .../crystal_kemal/custom_public/2.html | 0 .../fixtures/crystal_kemal/public/1.html | 0 .../fixtures/crystal_kemal/src/testapp.cr | 2 + .../testers/crystal_kemal_spec.cr | 4 +- .../analyzers/analyzer_crystal_kemal.cr | 46 +++++++++++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 spec/functional_test/fixtures/crystal_kemal/custom_public/2.html create mode 100644 spec/functional_test/fixtures/crystal_kemal/public/1.html diff --git a/spec/functional_test/fixtures/crystal_kemal/custom_public/2.html b/spec/functional_test/fixtures/crystal_kemal/custom_public/2.html new file mode 100644 index 00000000..e69de29b diff --git a/spec/functional_test/fixtures/crystal_kemal/public/1.html b/spec/functional_test/fixtures/crystal_kemal/public/1.html new file mode 100644 index 00000000..e69de29b diff --git a/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr b/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr index 787f08ac..05829a28 100644 --- a/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr +++ b/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr @@ -14,4 +14,6 @@ ws "/socket" do |socket| socket.send "Hello from Kemal!" end +public_folder "custom_public" + Kemal.run diff --git a/spec/functional_test/testers/crystal_kemal_spec.cr b/spec/functional_test/testers/crystal_kemal_spec.cr index 5359e6fe..08ebe642 100644 --- a/spec/functional_test/testers/crystal_kemal_spec.cr +++ b/spec/functional_test/testers/crystal_kemal_spec.cr @@ -7,9 +7,11 @@ extected_endpoints = [ Param.new("query", "", "form"), Param.new("my_auth", "", "cookie"), ]), + Endpoint.new("/1.html", "GET"), + Endpoint.new("/2.html", "GET"), ] FunctionalTester.new("fixtures/crystal_kemal/", { :techs => 1, - :endpoints => 3, + :endpoints => 5, }, extected_endpoints).test_all diff --git a/src/analyzer/analyzers/analyzer_crystal_kemal.cr b/src/analyzer/analyzers/analyzer_crystal_kemal.cr index 7914606f..f93a390a 100644 --- a/src/analyzer/analyzers/analyzer_crystal_kemal.cr +++ b/src/analyzer/analyzers/analyzer_crystal_kemal.cr @@ -2,6 +2,10 @@ require "../../models/analyzer" class AnalyzerCrystalKemal < Analyzer def analyze + # Variables + is_public = true + public_folders = [] of String + # Source Analysis begin Dir.glob("#{@base_path}/**/*") do |path| @@ -24,6 +28,25 @@ class AnalyzerCrystalKemal < Analyzer last_endpoint.push_param(param) end end + + if line.includes? "serve_static false" || "serve_static(false)" + is_public = false + end + + if line.includes? "public_folder" + begin + splited = line.split("public_folder") + public_folder = "" + + if splited.size > 1 + public_folder = splited[1].gsub("(", "").gsub(")", "").gsub(" ", "").gsub("\"", "").gsub("'", "") + if public_folder != "" + public_folders << public_folder + end + end + rescue + end + end end end end @@ -32,6 +55,29 @@ class AnalyzerCrystalKemal < Analyzer logger.debug e end + # Public Dir Analysis + if is_public + begin + Dir.glob("#{@base_path}/public/**/*") do |file| + next if File.directory?(file) + real_path = "#{@base_path}/public/".gsub(/\/+/, '/') + relative_path = file.sub(real_path, "") + @result << Endpoint.new("/#{relative_path}", "GET") + end + + public_folders.each do |folder| + Dir.glob("#{@base_path}/#{folder}/**/*") do |file| + next if File.directory?(file) + relative_path = get_relative_path(@base_path, file) + relative_path = get_relative_path(folder, relative_path) + @result << Endpoint.new("/#{relative_path}", "GET") + end + end + rescue e + logger.debug e + end + end + result end From 32f7704484e67ffd7f812059326280c9b90e60c5 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sun, 17 Mar 2024 18:10:19 +0900 Subject: [PATCH 05/29] Update SECURITY.md with improved vulnerability reporting instructions --- SECURITY.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 034e8480..c98a5bdd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,21 +1,19 @@ # Security Policy -## Supported Versions +## Reporting a Vulnerability -Use this section to tell people about which versions of your project are -currently being supported with security updates. +Found a security issue? Let us know so we can fix it. -| Version | Supported | -| ------- | ------------------ | -| 5.1.x | :white_check_mark: | -| 5.0.x | :x: | -| 4.0.x | :white_check_mark: | -| < 4.0 | :x: | +### How to Report -## Reporting a Vulnerability +* **For general security concerns**, please open a [GitHub issue](https://github.com/noir-cr/noir/issues). Use the `🛡️ security` label and describe the issue in as much detail as you can. This helps us to understand and address the problem more effectively. +* **For sensitive matters**, we encourage you to directly email the [noir team members](https://github.com/orgs/noir-cr/people). Handling these issues discreetly is vital for everyone's safety. + +### Our Team + +Beyond being passionate open source contributors, we are also seasoned Red Team security engineers. Our dual expertise means we're not only ready but also keen to address any security issues you might identify. Consider us your approachable security allies. Whether you notice something minor or more significant, we encourage you to get in touch. Open dialogue is key to us, and we're here to address any security concerns you might have—together. -Use this section to tell people how to report a vulnerability. +## Conclusion +Your vigilance and willingness to report security issues are what help keep our project robust and secure. We appreciate the time and effort you put into making our community a safer place. Remember, no concern is too small; we're here to listen and act. Together, we can ensure a secure environment for all our users and contributors. Thank you for being an essential part of our project's security. -Tell them where to go, how often they can expect to get an update on a -reported vulnerability, what to expect if the vulnerability is accepted or -declined, etc. +Thank you for your support in maintaining the security and integrity of our project! \ No newline at end of file From 0d5e03094c7924f77e3aa368587cc5911ac0e715 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Wed, 20 Mar 2024 23:56:05 +0900 Subject: [PATCH 06/29] Update build command in snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 37e25ad7..347b5478 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -23,7 +23,7 @@ parts: curl -fsSL https://crystal-lang.org/install.sh | sudo bash snapcraftctl pull shards install - shards build --release + shards build --release --no-debug --production cp ./bin/noir $SNAPCRAFT_PART_INSTALL/ snapcraftctl build build-packages: From fcbe6841adfe2342333116907dfd0aa0899fd6d9 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sat, 23 Mar 2024 01:03:38 +0900 Subject: [PATCH 07/29] Make Draft PR --- src/models/tag.cr | 11 +++++++++++ src/taggers/tagger.cr | 0 2 files changed, 11 insertions(+) create mode 100644 src/models/tag.cr create mode 100644 src/taggers/tagger.cr diff --git a/src/models/tag.cr b/src/models/tag.cr new file mode 100644 index 00000000..6db28260 --- /dev/null +++ b/src/models/tag.cr @@ -0,0 +1,11 @@ +require "json" +require "yaml" + +struct Tag + include JSON::Serializable + include YAML::Serializable + property name, description + + def initialize(@name : String, @description : String) + end +end diff --git a/src/taggers/tagger.cr b/src/taggers/tagger.cr new file mode 100644 index 00000000..e69de29b From b1d0efc0a76964de13499b0231354074c7b378ca Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sat, 23 Mar 2024 01:05:51 +0900 Subject: [PATCH 08/29] Update labeler --- .github/labeler.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index c8d85e5b..7fd724ab 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -29,6 +29,12 @@ - src/output_builder/** - src/models/output_builder.cr +🏷️ tagger: +- changed-files: + - any-glob-to-any-file: + - src/taggers/** + - src/models/tag.cr + 💊 spec: - changed-files: - any-glob-to-any-file: spec/** From 34f1be471422cb01149df332bf7126a3c1335ab4 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sat, 23 Mar 2024 21:37:01 +0900 Subject: [PATCH 09/29] Add Tag and Tagger models --- src/models/endpoint.cr | 26 ++++++++++++++++++++++++-- src/models/tag.cr | 11 ----------- src/models/tagger.cr | 24 ++++++++++++++++++++++++ src/taggers/tagger.cr | 2 ++ 4 files changed, 50 insertions(+), 13 deletions(-) delete mode 100644 src/models/tag.cr create mode 100644 src/models/tagger.cr diff --git a/src/models/endpoint.cr b/src/models/endpoint.cr index a3838281..6b9675b7 100644 --- a/src/models/endpoint.cr +++ b/src/models/endpoint.cr @@ -4,26 +4,30 @@ require "yaml" struct Endpoint include JSON::Serializable include YAML::Serializable - property url, method, params, protocol, details + property url, method, params, protocol, details, tags def initialize(@url : String, @method : String) @params = [] of Param @details = Details.new @protocol = "http" + @tags = [] of Tag end def initialize(@url : String, @method : String, @details : Details) @params = [] of Param @protocol = "http" + @tags = [] of Tag end def initialize(@url : String, @method : String, @params : Array(Param)) @details = Details.new @protocol = "http" + @tags = [] of Tag end def initialize(@url : String, @method : String, @params : Array(Param), @details : Details) @protocol = "http" + @tags = [] of Tag end def set_details(@details : Details) @@ -33,6 +37,10 @@ struct Endpoint @protocol = protocol end + def set_tag(tag : Tag) + @tags << tag + end + def push_param(param : Param) @params << param end @@ -54,11 +62,16 @@ end struct Param include JSON::Serializable include YAML::Serializable - property name, value, param_type + property name, value, param_type, tags # param_type can be "query", "json", "form", "header", "cookie" def initialize(@name : String, @value : String, @param_type : String) + @tags = [] of Tag + end + + def set_tag(tag : Tag) + @tags << tag end end @@ -111,3 +124,12 @@ struct EndpointReference def initialize(@endpoint : Endpoint, @metadata : Hash(Symbol, String)) end end + +struct Tag + include JSON::Serializable + include YAML::Serializable + property name, description + + def initialize(@name : String, @description : String) + end +end diff --git a/src/models/tag.cr b/src/models/tag.cr deleted file mode 100644 index 6db28260..00000000 --- a/src/models/tag.cr +++ /dev/null @@ -1,11 +0,0 @@ -require "json" -require "yaml" - -struct Tag - include JSON::Serializable - include YAML::Serializable - property name, description - - def initialize(@name : String, @description : String) - end -end diff --git a/src/models/tagger.cr b/src/models/tagger.cr new file mode 100644 index 00000000..242a724c --- /dev/null +++ b/src/models/tagger.cr @@ -0,0 +1,24 @@ +require "./logger" + +class Tager + @logger : NoirLogger + @options : Hash(Symbol, String) + @is_debug : Bool + @is_color : Bool + @is_log : Bool + + def initialize(options : Hash(Symbol, String)) + @is_debug = str_to_bool(options[:debug]) + @options = options + @is_color = str_to_bool(options[:color]) + @is_log = str_to_bool(options[:nolog]) + + @logger = NoirLogger.new @is_debug, @is_color, @is_log + end + + def perform(endpoints : Array(Endpoint)) : Array(Endpoint) + # After inheriting the class, write an action code here. + + endpoints + end +end diff --git a/src/taggers/tagger.cr b/src/taggers/tagger.cr index e69de29b..9dbe6318 100644 --- a/src/taggers/tagger.cr +++ b/src/taggers/tagger.cr @@ -0,0 +1,2 @@ +class Tagger +end From 8056184d12641f6f194a17786e80fef33589cc47 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sat, 23 Mar 2024 21:44:01 +0900 Subject: [PATCH 10/29] Fixed typo --- src/models/tagger.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/tagger.cr b/src/models/tagger.cr index 242a724c..936d913f 100644 --- a/src/models/tagger.cr +++ b/src/models/tagger.cr @@ -1,6 +1,6 @@ require "./logger" -class Tager +class Tagger @logger : NoirLogger @options : Hash(Symbol, String) @is_debug : Bool From 3efea430630093d92c62562d69e6aa16a93687e2 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sat, 23 Mar 2024 21:44:11 +0900 Subject: [PATCH 11/29] Add HuntParamTagger and remove Tagger class --- src/taggers/hunt_param.cr | 14 ++++++++++++++ src/taggers/tagger.cr | 2 -- 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 src/taggers/hunt_param.cr delete mode 100644 src/taggers/tagger.cr diff --git a/src/taggers/hunt_param.cr b/src/taggers/hunt_param.cr new file mode 100644 index 00000000..dcca5394 --- /dev/null +++ b/src/taggers/hunt_param.cr @@ -0,0 +1,14 @@ +require "../models/tagger" +require "../models/endpoint" + +class HuntParamTagger << Tagger + def perform(endpoints : Array(Endpoint)) : Array(Endpoint) + endpoints.each do |endpoint| + endpoint.params.each do |param| + # TODO + end + end + + endpoints + end +end diff --git a/src/taggers/tagger.cr b/src/taggers/tagger.cr deleted file mode 100644 index 9dbe6318..00000000 --- a/src/taggers/tagger.cr +++ /dev/null @@ -1,2 +0,0 @@ -class Tagger -end From 2ea6f0ddb3480164ef573f73748dbfba32d7ee6c Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sat, 23 Mar 2024 21:44:41 +0900 Subject: [PATCH 12/29] Refactor HuntParamTagger to remove unused variable --- src/taggers/hunt_param.cr | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/taggers/hunt_param.cr b/src/taggers/hunt_param.cr index dcca5394..a946d243 100644 --- a/src/taggers/hunt_param.cr +++ b/src/taggers/hunt_param.cr @@ -1,14 +1,14 @@ require "../models/tagger" require "../models/endpoint" -class HuntParamTagger << Tagger - def perform(endpoints : Array(Endpoint)) : Array(Endpoint) - endpoints.each do |endpoint| - endpoint.params.each do |param| - # TODO - end - end - - endpoints +class HuntParamTagger < Tagger + def perform(endpoints : Array(Endpoint)) : Array(Endpoint) + endpoints.each do |endpoint| + endpoint.params.each do |_| + # TODO + end end + + endpoints + end end From 0e8f309036036fe05afe8ef5e8a1379c715034ac Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sun, 24 Mar 2024 00:27:55 +0900 Subject: [PATCH 13/29] Add Tagger module and HuntParamTagger class --- spec/unit_test/tagger/tagger_spec.cr | 39 ++++++++++++++++++++++++++++ src/models/endpoint.cr | 4 +-- src/models/noir.cr | 2 ++ src/tagger/tagger.cr | 23 ++++++++++++++++ src/tagger/taggers/hunt_param.cr | 26 +++++++++++++++++++ src/taggers/hunt_param.cr | 14 ---------- 6 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 spec/unit_test/tagger/tagger_spec.cr create mode 100644 src/tagger/tagger.cr create mode 100644 src/tagger/taggers/hunt_param.cr delete mode 100644 src/taggers/hunt_param.cr diff --git a/spec/unit_test/tagger/tagger_spec.cr b/spec/unit_test/tagger/tagger_spec.cr new file mode 100644 index 00000000..90a429af --- /dev/null +++ b/spec/unit_test/tagger/tagger_spec.cr @@ -0,0 +1,39 @@ +require "../../../src/tagger/tagger" + +describe "Init" do + it "hunt_tagger" do + noir_options = default_options() + extected_endpoints = [ + Endpoint.new("/api/me", "GET", [ + Param.new("q", "", "query"), + Param.new("query", "", "query"), + Param.new("filter", "", "query"), + Param.new("X-Forwarded-For", "", "header"), + ]), + Endpoint.new("/api/sign_ups", "POST", [ + Param.new("url", "", "cookie"), + Param.new("command", "", "cookie"), + Param.new("role", "", "cookie"), + ]), + ] + run_tagger(extected_endpoints, noir_options) + extected_endpoints.each do |endpoint| + endpoint.params.each do |param| + case param.name + when "query" + param.tags.each do |tag| + tag.name.should eq("sqli") + end + when "url" + param.tags.each do |tag| + tag.name.should eq("ssrf") + end + when "role" + param.tags.each do |tag| + tag.name.should eq("sqli") + end + end + end + end + end +end diff --git a/src/models/endpoint.cr b/src/models/endpoint.cr index 6b9675b7..c77869a1 100644 --- a/src/models/endpoint.cr +++ b/src/models/endpoint.cr @@ -37,7 +37,7 @@ struct Endpoint @protocol = protocol end - def set_tag(tag : Tag) + def add_tag(tag : Tag) @tags << tag end @@ -70,7 +70,7 @@ struct Param @tags = [] of Tag end - def set_tag(tag : Tag) + def add_tag(tag : Tag) @tags << tag end end diff --git a/src/models/noir.cr b/src/models/noir.cr index fb0137b4..00fb8e5f 100644 --- a/src/models/noir.cr +++ b/src/models/noir.cr @@ -1,5 +1,6 @@ require "../detector/detector.cr" require "../analyzer/analyzer.cr" +require "../tagger/tagger.cr" require "../deliver/*" require "../output_builder/*" require "./endpoint.cr" @@ -91,6 +92,7 @@ class NoirRunner @endpoints = analysis_endpoints options, @techs, @logger optimize_endpoints combine_url_and_endpoints + run_tagger @endpoints, @options deliver end diff --git a/src/tagger/tagger.cr b/src/tagger/tagger.cr new file mode 100644 index 00000000..6fa387fb --- /dev/null +++ b/src/tagger/tagger.cr @@ -0,0 +1,23 @@ +require "./taggers/*" +require "../models/tagger" + +macro defind_taggers(taggers) + {% for tagger, index in taggers %} + instance = {{tagger}}.new(options) + tagger_list << instance + {% end %} +end + +def run_tagger(endpoints : Array(Endpoint), options : Hash(Symbol, String)) + tagger_list = [] of Tagger + + # Define taggers + defind_taggers([ + HuntParamTagger, + ]) + + # Run taggers + tagger_list.each do |tagger| + tagger.perform(endpoints) + end +end diff --git a/src/tagger/taggers/hunt_param.cr b/src/tagger/taggers/hunt_param.cr new file mode 100644 index 00000000..568239d5 --- /dev/null +++ b/src/tagger/taggers/hunt_param.cr @@ -0,0 +1,26 @@ +require "../../models/tagger" +require "../../models/endpoint" + +class HuntParamTagger < Tagger + def perform(endpoints : Array(Endpoint)) + tagger = Hash(String, Array(String)).new + tagger["ssti"] = ["template", "preview", "id", "view", "activity", "name", "content", "redirect"] + tagger["ssrf"] = ["dest", "redirect", "uri", "path", "continue", "url", "window", "next", "data", "reference", "site", "html", "val", "validate", "domain", "callback", "return", "page", "feed", "host", "port", "to", "out", "view", "dir", "show", "navigation", "open"] + tagger["sqli"] = ["id", "select", "report", "role", "update", "query", "user", "name", "sort", "where", "search", "params", "process", "row", "view", "table", "from", "sel", "results", "sleep", "fetch", "order", "keyword", "column", "field", "delete", "string", "number", "filter"] + tagger["idor"] = ["id", "user", "account", "number", "order", "no", "doc", "key", "email", "group", "profile", "edit", "report"] + tagger["file_inclusion"] = ["file", "document", "folder", "root", "path", "pg", "style", "pdf", "template", "php_path", "doc"] + tagger["debug"] = ["access", "admin", "dbg", "debug", "edit", "grant", "test", "alter", "clone", "create", "delete", "disable", "enable", "exec", "execute", "load", "make", "modify", "rename", "reset", "shell", "toggle", "adm", "root", "cfg", "config"] + tagger["command_injection"] = ["daemon", "host", "upload", "dir", "execute", "download", "log", "ip", "cli", "cmd"] + + endpoints.each do |endpoint| + endpoint.params.each do |param| + tagger.each do |key, values| + if values.includes? param.name + tag = Tag.new(key, "HUNT Param") + param.add_tag(tag) + end + end + end + end + end +end diff --git a/src/taggers/hunt_param.cr b/src/taggers/hunt_param.cr deleted file mode 100644 index a946d243..00000000 --- a/src/taggers/hunt_param.cr +++ /dev/null @@ -1,14 +0,0 @@ -require "../models/tagger" -require "../models/endpoint" - -class HuntParamTagger < Tagger - def perform(endpoints : Array(Endpoint)) : Array(Endpoint) - endpoints.each do |endpoint| - endpoint.params.each do |_| - # TODO - end - end - - endpoints - end -end From 583821802d841e381f9dbc544d444dda39d68378 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sun, 24 Mar 2024 00:45:31 +0900 Subject: [PATCH 14/29] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Add=20command-lin?= =?UTF-8?q?e=20options=20for=20tagger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/models/noir.cr | 4 +++- src/noir.cr | 3 +++ src/options.cr | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/models/noir.cr b/src/models/noir.cr index 00fb8e5f..d498193e 100644 --- a/src/models/noir.cr +++ b/src/models/noir.cr @@ -92,7 +92,9 @@ class NoirRunner @endpoints = analysis_endpoints options, @techs, @logger optimize_endpoints combine_url_and_endpoints - run_tagger @endpoints, @options + if @options[:tagger] == "yes" + run_tagger @endpoints, @options + end deliver end diff --git a/src/noir.cr b/src/noir.cr index a1d09736..6df84ce4 100644 --- a/src/noir.cr +++ b/src/noir.cr @@ -33,6 +33,9 @@ OptionParser.parse do |parser| noir_options[:nolog] = "yes" end + parser.separator "\n Tagger:".colorize(:blue) + parser.on "--tagger", "Use tagger" { |_| noir_options[:tagger] = "yes" } + parser.separator "\n Deliver:".colorize(:blue) parser.on "--send-req", "Send results to a web request" { |_| noir_options[:send_req] = "yes" } parser.on "--send-proxy http://proxy..", "Send results to a web request via an HTTP proxy" { |var| noir_options[:send_proxy] = var } diff --git a/src/options.cr b/src/options.cr index f323d82c..93280d1c 100644 --- a/src/options.cr +++ b/src/options.cr @@ -19,6 +19,8 @@ def default_options :url => "", :use_filters => "", :use_matchers => "", + :tagger => "no", + :use_taggers => "", } noir_options From 8d182a2960b3943eaed268faefd0ae9cc677e2ca Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sun, 24 Mar 2024 23:53:18 +0900 Subject: [PATCH 15/29] Refactor tagger to use constant definitions --- src/models/endpoint.cr | 4 +-- src/tagger/taggers/hunt_param.cr | 49 +++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/models/endpoint.cr b/src/models/endpoint.cr index c77869a1..c79b7d73 100644 --- a/src/models/endpoint.cr +++ b/src/models/endpoint.cr @@ -128,8 +128,8 @@ end struct Tag include JSON::Serializable include YAML::Serializable - property name, description + property name, description, tagger - def initialize(@name : String, @description : String) + def initialize(@name : String, @description : String, @tagger : String) end end diff --git a/src/tagger/taggers/hunt_param.cr b/src/tagger/taggers/hunt_param.cr index 568239d5..b1b976bd 100644 --- a/src/tagger/taggers/hunt_param.cr +++ b/src/tagger/taggers/hunt_param.cr @@ -2,21 +2,48 @@ require "../../models/tagger" require "../../models/endpoint" class HuntParamTagger < Tagger + TAG_DEFINITIONS = { + "ssti" => { + "words" => ["template", "preview", "id", "view", "activity", "name", "content", "redirect"], + "description" => "This parameter may be vulnerable to Server Side Template Injection (SSTI) attacks.", + }, + "ssrf" => { + "words" => ["dest", "redirect", "uri", "path", "continue", "url", "window", "next", "data", "reference", "site", "html", "val", "validate", "domain", "callback", "return", "page", "feed", "host", "port", "to", "out", "view", "dir", "show", "navigation", "open"], + "description" => "This parameter may be vulnerable to Server Side Request Forgery (SSRF) attacks.", + }, + "sqli" => { + "words" => ["id", "select", "report", "role", "update", "query", "user", "name", "sort", "where", "search", "params", "process", "row", "view", "table", "from", "sel", "results", "sleep", "fetch", "order", "keyword", "column", "field", "delete", "string", "number", "filter"], + "description" => "This parameter may be vulnerable to SQL Injection attacks.", + }, + "idor" => { + "words" => ["id", "user", "account", "number", "order", "no", "doc", "key", "email", "group", "profile", "edit", "report"], + "description" => "This parameter may be vulnerable to Insecure Direct Object Reference (IDOR) attacks.", + }, + "file-inclusion" => { + "words" => ["file", "document", "folder", "root", "path", "pg", "style", "pdf", "template", "php_path", "doc"], + "description" => "This parameter may be vulnerable to File Inclusion attacks.", + }, + "debug" => { + "words" => ["access", "admin", "dbg", "debug", "edit", "grant", "test", "alter", "clone", "create", "delete", "disable", "enable", "exec", "execute", "load", "make", "modify", "rename", "reset", "shell", "toggle", "adm", "root", "cfg", "config"], + "description" => "This parameter may be vulnerable to Debug method exploits.", + }, + "command-injection" => { + "words" => ["daemon", "host", "upload", "dir", "execute", "download", "log", "ip", "cli", "cmd"], + "description" => "This parameter may be vulnerable to Command Injection attacks.", + }, + } + def perform(endpoints : Array(Endpoint)) - tagger = Hash(String, Array(String)).new - tagger["ssti"] = ["template", "preview", "id", "view", "activity", "name", "content", "redirect"] - tagger["ssrf"] = ["dest", "redirect", "uri", "path", "continue", "url", "window", "next", "data", "reference", "site", "html", "val", "validate", "domain", "callback", "return", "page", "feed", "host", "port", "to", "out", "view", "dir", "show", "navigation", "open"] - tagger["sqli"] = ["id", "select", "report", "role", "update", "query", "user", "name", "sort", "where", "search", "params", "process", "row", "view", "table", "from", "sel", "results", "sleep", "fetch", "order", "keyword", "column", "field", "delete", "string", "number", "filter"] - tagger["idor"] = ["id", "user", "account", "number", "order", "no", "doc", "key", "email", "group", "profile", "edit", "report"] - tagger["file_inclusion"] = ["file", "document", "folder", "root", "path", "pg", "style", "pdf", "template", "php_path", "doc"] - tagger["debug"] = ["access", "admin", "dbg", "debug", "edit", "grant", "test", "alter", "clone", "create", "delete", "disable", "enable", "exec", "execute", "load", "make", "modify", "rename", "reset", "shell", "toggle", "adm", "root", "cfg", "config"] - tagger["command_injection"] = ["daemon", "host", "upload", "dir", "execute", "download", "log", "ip", "cli", "cmd"] + tagger = {} of String => Hash(String, Array(String) | String) + TAG_DEFINITIONS.each do |key, value| + tagger[key] = {"words" => value["words"], "description" => value["description"]} + end endpoints.each do |endpoint| endpoint.params.each do |param| - tagger.each do |key, values| - if values.includes? param.name - tag = Tag.new(key, "HUNT Param") + TAG_DEFINITIONS.each do |k, v| + if v["words"].includes? param.name + tag = Tag.new(k, v["description"].to_s, "Hunt") param.add_tag(tag) end end From c16c1fd30aef1986b99bd9c1bfbedfd37969d2b7 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 13:43:39 +0900 Subject: [PATCH 16/29] Add support for tags in OutputBuilder --- src/models/output_builder.cr | 8 ++++++++ src/output_builder/common.cr | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/src/models/output_builder.cr b/src/models/output_builder.cr index fd9600c8..b18b3d23 100644 --- a/src/models/output_builder.cr +++ b/src/models/output_builder.cr @@ -38,6 +38,7 @@ class OutputBuilder final_body = "" final_headers = [] of String final_cookies = [] of String + final_tags = [] of String is_json = false first_query = true first_form = true @@ -73,6 +74,12 @@ class OutputBuilder if param.param_type == "json" is_json = true end + + if param.tags.size > 0 + param.tags.each do |tag| + final_tags << tag.name + end + end end if is_json @@ -95,6 +102,7 @@ class OutputBuilder body: final_body, header: final_headers, cookie: final_cookies, + tags: final_tags.uniq, body_type: is_json ? "json" : "form", } end diff --git a/src/output_builder/common.cr b/src/output_builder/common.cr index a0ee7475..f98bcace 100644 --- a/src/output_builder/common.cr +++ b/src/output_builder/common.cr @@ -31,6 +31,11 @@ class OutputBuilderCommon < OutputBuilder r_buffer += "\n ○ body: #{r_body}" end + if baked[:tags].size > 0 + r_tags = baked[:tags].join(" ").colorize(:light_magenta).toggle(@is_color) + r_buffer += "\n ○ tags: #{r_tags}" + end + if @options[:include_path] == "yes" details = endpoint.details if details.code_paths && details.code_paths.size > 0 From b03de82a33b5fa3431c2f201bb9fa7af14194b69 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 14:46:30 +0900 Subject: [PATCH 17/29] Add tagger functionality and update options --- src/models/noir.cr | 13 +++++++++-- src/models/tagger.cr | 6 +++++ src/noir.cr | 14 +++++++++++- src/options.cr | 2 +- src/tagger/tagger.cr | 39 +++++++++++++++++++++++--------- src/tagger/taggers/hunt_param.cr | 5 ++++ 6 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/models/noir.cr b/src/models/noir.cr index d498193e..a5cb071a 100644 --- a/src/models/noir.cr +++ b/src/models/noir.cr @@ -92,9 +92,18 @@ class NoirRunner @endpoints = analysis_endpoints options, @techs, @logger optimize_endpoints combine_url_and_endpoints - if @options[:tagger] == "yes" - run_tagger @endpoints, @options + + # Run tagger + if @options[:all_taggers] == "yes" + @logger.info "Running all taggers." + NoirTaggers.run_tagger @endpoints, @options, "all" + else + @options[:use_taggers] != "" + @logger.info "Running #{@options[:use_taggers]} taggers." + NoirTaggers.run_tagger @endpoints, @options, @options[:use_taggers] end + + # Run deliver deliver end diff --git a/src/models/tagger.cr b/src/models/tagger.cr index 936d913f..51ab59bd 100644 --- a/src/models/tagger.cr +++ b/src/models/tagger.cr @@ -6,16 +6,22 @@ class Tagger @is_debug : Bool @is_color : Bool @is_log : Bool + @name : String def initialize(options : Hash(Symbol, String)) @is_debug = str_to_bool(options[:debug]) @options = options @is_color = str_to_bool(options[:color]) @is_log = str_to_bool(options[:nolog]) + @name = "" @logger = NoirLogger.new @is_debug, @is_color, @is_log end + def name + @name + end + def perform(endpoints : Array(Endpoint)) : Array(Endpoint) # After inheriting the class, write an action code here. diff --git a/src/noir.cr b/src/noir.cr index 6df84ce4..22b33796 100644 --- a/src/noir.cr +++ b/src/noir.cr @@ -34,7 +34,19 @@ OptionParser.parse do |parser| end parser.separator "\n Tagger:".colorize(:blue) - parser.on "--tagger", "Use tagger" { |_| noir_options[:tagger] = "yes" } + parser.on "-T", "--use-all-taggers", "Activates all taggers for full analysis coverage" { |_| noir_options[:all_taggers] = "yes" } + parser.on "--use-taggers VALUES", "Activates specific taggers (e.g., --use-taggers hunt,etc)" { |var| noir_options[:use_taggers] = var } + parser.on "--list-taggers", "Lists all available taggers" do + puts "Available taggers:" + techs = NoirTaggers.get_taggers + techs.each do |tagger, value| + puts " #{tagger.to_s.colorize(:green)}" + value.each do |k, v| + puts " #{k.to_s.colorize(:blue)}: #{v}" + end + end + exit + end parser.separator "\n Deliver:".colorize(:blue) parser.on "--send-req", "Send results to a web request" { |_| noir_options[:send_req] = "yes" } diff --git a/src/options.cr b/src/options.cr index 93280d1c..764c7a29 100644 --- a/src/options.cr +++ b/src/options.cr @@ -19,7 +19,7 @@ def default_options :url => "", :use_filters => "", :use_matchers => "", - :tagger => "no", + :all_taggers => "no", :use_taggers => "", } diff --git a/src/tagger/tagger.cr b/src/tagger/tagger.cr index 6fa387fb..179a90cc 100644 --- a/src/tagger/tagger.cr +++ b/src/tagger/tagger.cr @@ -1,23 +1,40 @@ require "./taggers/*" require "../models/tagger" -macro defind_taggers(taggers) - {% for tagger, index in taggers %} +macro define_taggers(*taggers) + {% for tagger in taggers %} instance = {{tagger}}.new(options) tagger_list << instance {% end %} end -def run_tagger(endpoints : Array(Endpoint), options : Hash(Symbol, String)) - tagger_list = [] of Tagger +module NoirTaggers + HasTaggers = { + :hunt => { + :name => "HuntParam Tagger", + :desc => "Identifies common parameters vulnerable to certain vulnerability classes", + :class => HuntParamTagger, + }, + } - # Define taggers - defind_taggers([ - HuntParamTagger, - ]) + def self.get_taggers + HasTaggers + end + + def self.run_tagger(endpoints : Array(Endpoint), options : Hash(Symbol, String), use_taggers : String) + tagger_list = [] of Tagger # This will hold instances of taggers + + # Define taggers by creating instances + # Assuming HuntParamTagger is defined and is the only tagger + define_taggers(HuntParamTagger) + + # Parsing use_taggers + use_taggers_arr = use_taggers.split(",") + use_taggers_arr = use_taggers_arr.map(&.strip) - # Run taggers - tagger_list.each do |tagger| - tagger.perform(endpoints) + # Run taggers + tagger_list.each do |tagger| + tagger.perform(endpoints) if use_taggers_arr.includes?(tagger.name) || use_taggers_arr.includes?("all") + end end end diff --git a/src/tagger/taggers/hunt_param.cr b/src/tagger/taggers/hunt_param.cr index b1b976bd..c6486b42 100644 --- a/src/tagger/taggers/hunt_param.cr +++ b/src/tagger/taggers/hunt_param.cr @@ -33,6 +33,11 @@ class HuntParamTagger < Tagger }, } + def initialize(options : Hash(Symbol, String)) + super + @name = "hunt" + end + def perform(endpoints : Array(Endpoint)) tagger = {} of String => Hash(String, Array(String) | String) TAG_DEFINITIONS.each do |key, value| From 5e7393d8900f92bd771252498fca7de474ee72c4 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 14:48:07 +0900 Subject: [PATCH 18/29] Update README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3d5ab1b6..b69eb9d1 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,11 @@ Usage: noir --no-color Disable color output --no-log Displaying only the results + Tagger: + -T, --use-all-taggers Activates all taggers for full analysis coverage + --use-taggers VALUES Activates specific taggers (e.g., --use-taggers hunt,etc) + --list-taggers Lists all available taggers + Deliver: --send-req Send results to a web request --send-proxy http://proxy.. Send results to a web request via an HTTP proxy From dc15825b90d0e04e696426d3a7ff546e657151fd Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 14:49:45 +0900 Subject: [PATCH 19/29] Fix run_tagger method call in tagger_spec.cr --- spec/unit_test/tagger/tagger_spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit_test/tagger/tagger_spec.cr b/spec/unit_test/tagger/tagger_spec.cr index 90a429af..e498b9b9 100644 --- a/spec/unit_test/tagger/tagger_spec.cr +++ b/spec/unit_test/tagger/tagger_spec.cr @@ -16,7 +16,7 @@ describe "Init" do Param.new("role", "", "cookie"), ]), ] - run_tagger(extected_endpoints, noir_options) + NoirTaggers.run_tagger(extected_endpoints, noir_options, "all") extected_endpoints.each do |endpoint| endpoint.params.each do |param| case param.name From fb58668c756ebc0296c993d40fb169f893f8fdb7 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 18:00:30 +0900 Subject: [PATCH 20/29] Add oauth tagger --- spec/unit_test/tagger/tagger_spec.cr | 21 ++++++++++++++++++-- src/noir.cr | 2 +- src/tagger/tagger.cr | 13 +++++++++++++ src/tagger/taggers/oauth.cr | 29 ++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/tagger/taggers/oauth.cr diff --git a/spec/unit_test/tagger/tagger_spec.cr b/spec/unit_test/tagger/tagger_spec.cr index e498b9b9..e2ea14b6 100644 --- a/spec/unit_test/tagger/tagger_spec.cr +++ b/spec/unit_test/tagger/tagger_spec.cr @@ -1,6 +1,6 @@ require "../../../src/tagger/tagger" -describe "Init" do +describe "Tagger" do it "hunt_tagger" do noir_options = default_options() extected_endpoints = [ @@ -16,7 +16,7 @@ describe "Init" do Param.new("role", "", "cookie"), ]), ] - NoirTaggers.run_tagger(extected_endpoints, noir_options, "all") + NoirTaggers.run_tagger(extected_endpoints, noir_options, "hunt") extected_endpoints.each do |endpoint| endpoint.params.each do |param| case param.name @@ -36,4 +36,21 @@ describe "Init" do end end end + + it "oauth_tagger" do + noir_options = default_options() + extected_endpoints = [ + Endpoint.new("/token", "GET", [ + Param.new("client_id", "", "query"), + Param.new("grant_type", "", "query"), + Param.new("code", "", "query"), + ]), + ] + NoirTaggers.run_tagger(extected_endpoints, noir_options, "oauth") + extected_endpoints.each do |endpoint| + endpoint.tags.each do |tag| + tag.name.should eq("oauth") + end + end + end end diff --git a/src/noir.cr b/src/noir.cr index 22b33796..b6d87186 100644 --- a/src/noir.cr +++ b/src/noir.cr @@ -35,7 +35,7 @@ OptionParser.parse do |parser| parser.separator "\n Tagger:".colorize(:blue) parser.on "-T", "--use-all-taggers", "Activates all taggers for full analysis coverage" { |_| noir_options[:all_taggers] = "yes" } - parser.on "--use-taggers VALUES", "Activates specific taggers (e.g., --use-taggers hunt,etc)" { |var| noir_options[:use_taggers] = var } + parser.on "--use-taggers VALUES", "Activates specific taggers (e.g., --use-taggers hunt,oauth)" { |var| noir_options[:use_taggers] = var } parser.on "--list-taggers", "Lists all available taggers" do puts "Available taggers:" techs = NoirTaggers.get_taggers diff --git a/src/tagger/tagger.cr b/src/tagger/tagger.cr index 179a90cc..cfdbf039 100644 --- a/src/tagger/tagger.cr +++ b/src/tagger/tagger.cr @@ -15,6 +15,11 @@ module NoirTaggers :desc => "Identifies common parameters vulnerable to certain vulnerability classes", :class => HuntParamTagger, }, + :oauth => { + :name => "OAuth Tagger", + :desc => "Identifies OAuth endpoints", + :class => OAuthTagger, + }, } def self.get_taggers @@ -27,6 +32,14 @@ module NoirTaggers # Define taggers by creating instances # Assuming HuntParamTagger is defined and is the only tagger define_taggers(HuntParamTagger) + define_taggers(OAuthTagger) + # HasTaggers.each_value do |tagger| + # if tagger[:class].class.to_s == "Class" + # instance = tagger[:class].new(options) + # tagger_list << instance + # end + # end + # Error: wrong number of arguments for 'String#new' (given 1, expected 0) ?? # Parsing use_taggers use_taggers_arr = use_taggers.split(",") diff --git a/src/tagger/taggers/oauth.cr b/src/tagger/taggers/oauth.cr new file mode 100644 index 00000000..6c90c0fb --- /dev/null +++ b/src/tagger/taggers/oauth.cr @@ -0,0 +1,29 @@ +require "../../models/tagger" +require "../../models/endpoint" + +class OAuthTagger < Tagger + WORDS = ["grant_type", "code", "redirect_uri", "client_id", "client_secret"] + + def initialize(options : Hash(Symbol, String)) + super + @name = "oauth" + end + + def perform(endpoints : Array(Endpoint)) + endpoints.each do |endpoint| + tmp_params = [] of String + + endpoint.params.each do |param| + tmp_params.push param.name.to_s + end + + # Check that at least three parameters match. + check = (WORDS & tmp_params).size >= 3 + + if check + tag = Tag.new("oauth", "Suspected OAuth endpoint for granting 3rd party access.", "Oauth") + endpoint.add_tag(tag) + end + end + end +end From 62a233e10ee9cdb211a922d0e6d6a50c64d0549f Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 18:15:30 +0900 Subject: [PATCH 21/29] Refactor tagger.cr to use runner instead of class --- src/tagger/tagger.cr | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/tagger/tagger.cr b/src/tagger/tagger.cr index cfdbf039..2e8f60b1 100644 --- a/src/tagger/tagger.cr +++ b/src/tagger/tagger.cr @@ -10,15 +10,15 @@ end module NoirTaggers HasTaggers = { - :hunt => { - :name => "HuntParam Tagger", - :desc => "Identifies common parameters vulnerable to certain vulnerability classes", - :class => HuntParamTagger, + hunt: { + name: "HuntParam Tagger", + desc: "Identifies common parameters vulnerable to certain vulnerability classes", + runner: HuntParamTagger, }, - :oauth => { - :name => "OAuth Tagger", - :desc => "Identifies OAuth endpoints", - :class => OAuthTagger, + oauth: { + name: "OAuth Tagger", + desc: "Identifies OAuth endpoints", + runner: OAuthTagger, }, } @@ -31,15 +31,12 @@ module NoirTaggers # Define taggers by creating instances # Assuming HuntParamTagger is defined and is the only tagger - define_taggers(HuntParamTagger) - define_taggers(OAuthTagger) - # HasTaggers.each_value do |tagger| - # if tagger[:class].class.to_s == "Class" - # instance = tagger[:class].new(options) - # tagger_list << instance - # end - # end - # Error: wrong number of arguments for 'String#new' (given 1, expected 0) ?? + HasTaggers.each_value do |tagger| + if tagger[:runner].class.to_s == "Class" + instance = tagger[:runner].new(options) + tagger_list << instance + end + end # Parsing use_taggers use_taggers_arr = use_taggers.split(",") From d54a76aad4fc3ad333df3b2e72a59636361e27be Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 18:16:26 +0900 Subject: [PATCH 22/29] Refactor tagger.cr to improve readability and consistency --- src/tagger/tagger.cr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tagger/tagger.cr b/src/tagger/tagger.cr index 2e8f60b1..7d9ad94c 100644 --- a/src/tagger/tagger.cr +++ b/src/tagger/tagger.cr @@ -11,13 +11,13 @@ end module NoirTaggers HasTaggers = { hunt: { - name: "HuntParam Tagger", - desc: "Identifies common parameters vulnerable to certain vulnerability classes", + name: "HuntParam Tagger", + desc: "Identifies common parameters vulnerable to certain vulnerability classes", runner: HuntParamTagger, }, oauth: { - name: "OAuth Tagger", - desc: "Identifies OAuth endpoints", + name: "OAuth Tagger", + desc: "Identifies OAuth endpoints", runner: OAuthTagger, }, } From e3f615a92b9827cbc8b55cf5353eba023e67250d Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 18:44:36 +0900 Subject: [PATCH 23/29] Refactor tagger logging and add debug information --- src/models/noir.cr | 8 ++++++-- src/models/output_builder.cr | 7 ++++++- src/tagger/tagger.cr | 7 ------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/models/noir.cr b/src/models/noir.cr index a5cb071a..d2fb7ebf 100644 --- a/src/models/noir.cr +++ b/src/models/noir.cr @@ -97,8 +97,12 @@ class NoirRunner if @options[:all_taggers] == "yes" @logger.info "Running all taggers." NoirTaggers.run_tagger @endpoints, @options, "all" - else - @options[:use_taggers] != "" + if @is_debug + NoirTaggers.get_taggers.each do |tagger| + @logger.debug "Tagger: #{tagger}" + end + end + elsif @options[:use_taggers] != "" @logger.info "Running #{@options[:use_taggers]} taggers." NoirTaggers.run_tagger @endpoints, @options, @options[:use_taggers] end diff --git a/src/models/output_builder.cr b/src/models/output_builder.cr index b18b3d23..705009c1 100644 --- a/src/models/output_builder.cr +++ b/src/models/output_builder.cr @@ -95,7 +95,12 @@ class OutputBuilder end end - @logger.debug "Baked endpoint #{final_url} with #{final_body} body and #{final_headers.size} headers." + @logger.debug "Baked endpoints" + @logger.debug " + Final URL: #{final_url}" + @logger.debug " + Body: #{final_body}" + @logger.debug " + Headers: #{final_headers}" + @logger.debug " + Cookies: #{final_cookies}" + @logger.debug " + Tags: #{final_tags}" { url: final_url, diff --git a/src/tagger/tagger.cr b/src/tagger/tagger.cr index 7d9ad94c..26030ff0 100644 --- a/src/tagger/tagger.cr +++ b/src/tagger/tagger.cr @@ -1,13 +1,6 @@ require "./taggers/*" require "../models/tagger" -macro define_taggers(*taggers) - {% for tagger in taggers %} - instance = {{tagger}}.new(options) - tagger_list << instance - {% end %} -end - module NoirTaggers HasTaggers = { hunt: { From 396958476db74457352e49bed92afe2cd259e8f5 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 18:49:06 +0900 Subject: [PATCH 24/29] Update version to 0.14.0 in shard.yml and snapcraft.yaml, and in src/noir.cr --- shard.yml | 2 +- snap/snapcraft.yaml | 2 +- src/noir.cr | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shard.yml b/shard.yml index 2330c826..bb2aa871 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: noir -version: 0.13.0 +version: 0.14.0 authors: - hahwul diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 347b5478..3ecae15f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: noir base: core20 -version: 0.13.0 +version: 0.14.0 summary: Attack surface detector that identifies endpoints by static analysis. description: | Noir is an open-source project specializing in identifying attack surfaces for enhanced whitebox security testing and security pipeline. diff --git a/src/noir.cr b/src/noir.cr index b6d87186..a3b6beb9 100644 --- a/src/noir.cr +++ b/src/noir.cr @@ -6,7 +6,7 @@ require "./options.cr" require "./techs/techs.cr" module Noir - VERSION = "0.13.0" + VERSION = "0.14.0" end noir_options = default_options() From fe2e6aa78fad11eb352d3c52bdc257ea1216d0ba Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 18:52:15 +0900 Subject: [PATCH 25/29] Add /token endpoint for authentication for testcode --- spec/functional_test/fixtures/crystal_kemal/src/testapp.cr | 6 ++++++ spec/functional_test/testers/crystal_kemal_spec.cr | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr b/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr index 05829a28..89446b21 100644 --- a/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr +++ b/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr @@ -10,6 +10,12 @@ post "/query" do env.params.body["query"].as(String) end +post "/token" do + env.params.body["client_id"].as(String) + env.params.body["redirect_url"].as(String) + env.params.body["grant_type"].as(String) +end + ws "/socket" do |socket| socket.send "Hello from Kemal!" end diff --git a/spec/functional_test/testers/crystal_kemal_spec.cr b/spec/functional_test/testers/crystal_kemal_spec.cr index 08ebe642..0ea36423 100644 --- a/spec/functional_test/testers/crystal_kemal_spec.cr +++ b/spec/functional_test/testers/crystal_kemal_spec.cr @@ -7,11 +7,16 @@ extected_endpoints = [ Param.new("query", "", "form"), Param.new("my_auth", "", "cookie"), ]), + Endpoint.new("/token", "POST", [ + Param.new("grant_type", "", "form"), + Param.new("redirect_url", "", "form"), + Param.new("client_id", "", "form"), + ]), Endpoint.new("/1.html", "GET"), Endpoint.new("/2.html", "GET"), ] FunctionalTester.new("fixtures/crystal_kemal/", { :techs => 1, - :endpoints => 5, + :endpoints => 6, }, extected_endpoints).test_all From 27b1b5f0710e83f188220bf0500f376b9de1196b Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 19:08:13 +0900 Subject: [PATCH 26/29] Fixed bug --- .../fixtures/crystal_kemal/src/testapp.cr | 2 +- spec/functional_test/testers/crystal_kemal_spec.cr | 2 +- src/output_builder/common.cr | 9 +++++++-- src/tagger/taggers/oauth.cr | 8 ++++++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr b/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr index 89446b21..0b9a46f2 100644 --- a/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr +++ b/spec/functional_test/fixtures/crystal_kemal/src/testapp.cr @@ -10,7 +10,7 @@ post "/query" do env.params.body["query"].as(String) end -post "/token" do +get "/token" do env.params.body["client_id"].as(String) env.params.body["redirect_url"].as(String) env.params.body["grant_type"].as(String) diff --git a/spec/functional_test/testers/crystal_kemal_spec.cr b/spec/functional_test/testers/crystal_kemal_spec.cr index 0ea36423..7da85e4c 100644 --- a/spec/functional_test/testers/crystal_kemal_spec.cr +++ b/spec/functional_test/testers/crystal_kemal_spec.cr @@ -7,7 +7,7 @@ extected_endpoints = [ Param.new("query", "", "form"), Param.new("my_auth", "", "cookie"), ]), - Endpoint.new("/token", "POST", [ + Endpoint.new("/token", "GET", [ Param.new("grant_type", "", "form"), Param.new("redirect_url", "", "form"), Param.new("client_id", "", "form"), diff --git a/src/output_builder/common.cr b/src/output_builder/common.cr index f98bcace..3ca81ac1 100644 --- a/src/output_builder/common.cr +++ b/src/output_builder/common.cr @@ -31,8 +31,13 @@ class OutputBuilderCommon < OutputBuilder r_buffer += "\n ○ body: #{r_body}" end - if baked[:tags].size > 0 - r_tags = baked[:tags].join(" ").colorize(:light_magenta).toggle(@is_color) + tags = baked[:tags] + endpoint.tags.each do |tag| + tags << tag.name.to_s + end + + if tags.size > 0 + r_tags = tags.join(" ").colorize(:light_magenta).toggle(@is_color) r_buffer += "\n ○ tags: #{r_tags}" end diff --git a/src/tagger/taggers/oauth.cr b/src/tagger/taggers/oauth.cr index 6c90c0fb..7697b7fd 100644 --- a/src/tagger/taggers/oauth.cr +++ b/src/tagger/taggers/oauth.cr @@ -2,7 +2,7 @@ require "../../models/tagger" require "../../models/endpoint" class OAuthTagger < Tagger - WORDS = ["grant_type", "code", "redirect_uri", "client_id", "client_secret"] + WORDS = ["grant_type", "code", "redirect_uri", "redirect_url", "client_id", "client_secret"] def initialize(options : Hash(Symbol, String)) super @@ -17,8 +17,12 @@ class OAuthTagger < Tagger tmp_params.push param.name.to_s end + words_set = Set.new(WORDS) + tmp_params_set = Set.new(tmp_params) + intersection = words_set & tmp_params_set + # Check that at least three parameters match. - check = (WORDS & tmp_params).size >= 3 + check = intersection.size.to_i >= 3 if check tag = Tag.new("oauth", "Suspected OAuth endpoint for granting 3rd party access.", "Oauth") From e694efac4b6f144dac8876d9b8a9ba7109b29f17 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 23:07:27 +0900 Subject: [PATCH 27/29] Update README --- README.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b69eb9d1..4f357284 100644 --- a/README.md +++ b/README.md @@ -146,51 +146,51 @@ Usage: noir Example ```bash -noir -b . -u https://testapp.internal.domains +noir -b . -u https://testapp.internal.domains -T ``` -![](https://github.com/noir-cr/noir/assets/13212227/40d09acf-e250-4ea9-a84b-d9251a2d5147) +![](https://github.com/noir-cr/noir/assets/13212227/4e69da04-d585-4745-9cc7-ef6e69e193b0) JSON Result ``` -noir -b . -u https://testapp.internal.domains -f json +noir -b . -u https://testapp.internal.domains -f json -T ``` ```json -[ - ... - { - "headers": [], +{ + "url": "https://testapp.internal.domains/query", "method": "POST", "params": [ { - "name": "article_slug", - "param_type": "json", - "value": "" - }, - { - "name": "X-API-KEY", - "value":"", - "param_type":"header" + "name": "my_auth", + "value": "", + "param_type": "cookie", + "tags": [] }, { - "name": "auth", - "param_type": "cookie", - "value": "" + "name": "query", + "value": "", + "param_type": "form", + "tags": [ + { + "name": "sqli", + "description": "This parameter may be vulnerable to SQL Injection attacks.", + "tagger": "Hunt" + } + ] } ], - "protocol": "http", - "url": "https://testapp.internal.domains/comments", "details": { "code_paths": [ { - "path": "app_source/testapp.cr", - "line": 3 + "path": "spec/functional_test/fixtures/crystal_kemal/src/testapp.cr", + "line": 8 } ] - } + }, + "protocol": "http", + "tags": [] } -] ``` ### Contributing From 891de78b7349a5b4b556588b435620d056164a39 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Fri, 29 Mar 2024 23:57:28 +0900 Subject: [PATCH 28/29] Update README --- README.md | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4f357284..da97ef78 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,22 @@
-

Noir is an attack surface detector that identifies endpoints by static analysis.

+

Attack surface detector that identifies endpoints by static analysis.

+

+ + + +

+ +

+ Key Features • + Available Support Scope • + Installation • + Usage • + Contributing +

+ ## Key Features - Automatically identify language and framework from source code. - Find API endpoints and web pages through code analysis. @@ -10,7 +24,10 @@ - That provides structured data such as JSON and YAML for identified Attack Surfaces to enable seamless interaction with other tools. Also provides command line samples to easily integrate and collaborate with other tools, such as curls or httpie. ## Available Support Scope -### Endpoint's Entities + +
+ Endpoint's Entities + - Path - Method - Param @@ -19,7 +36,10 @@ - Protocol (e.g ws) - Details (e.g The origin of the endpoint) -### Languages and Frameworks +
+ +
+ Languages and Frameworks | Language | Framework | URL | Method | Param | Header | Cookie | WS | |----------|-------------|-----|--------|-------|--------|--------|----| @@ -45,8 +65,10 @@ | C# | ASP.NET MVC | ✅ | X | X | X | X | X | | JS | Next | X | X | X | X | X | X | +
-### Specification +
+ Specification | Specification | Format | URL | Method | Param | Header | WS | |------------------------|---------|-----|--------|-------|--------|----| @@ -57,6 +79,8 @@ | RAML | YAML | ✅ | ✅ | ✅ | ✅ | X | | HAR | JSON | ✅ | ✅ | ✅ | ✅ | X | +
+ ## Installation ### Homebrew (macOS) @@ -193,7 +217,7 @@ noir -b . -u https://testapp.internal.domains -f json -T } ``` -### Contributing +## Contributing Noir is open-source project and made it with ❤️ if you want contribute this project, please see [CONTRIBUTING.md](./CONTRIBUTING.md) and Pull-Request with cool your contents. From 60e0cf995e63cf90db613c09f909139ad5aecf92 Mon Sep 17 00:00:00 2001 From: HAHWUL Date: Sat, 30 Mar 2024 00:01:27 +0900 Subject: [PATCH 29/29] Update README --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index da97ef78..36b8c566 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,12 @@

- - - + + + + + +