From a40fc1d8ee6ab1235048543b5d55e79ec3662ea9 Mon Sep 17 00:00:00 2001 From: "d.lukshto" Date: Sat, 10 Sep 2022 12:06:41 +0300 Subject: [PATCH 1/8] add dock and samples --- README.md | 138 ++++++++++++++++++++++++++++-- examples/abstract_checker.py | 63 ++++++++++++++ checker.py => examples/checker.py | 0 3 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 examples/abstract_checker.py rename checker.py => examples/checker.py (100%) diff --git a/README.md b/README.md index 50122e5..7eab917 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # Gornilo-Checker -This is a checker wrapper lib. +Gornilo is a checker wrapper lib. #### Features: - - no exit-code/chk-sys interface requirements - object model - built-in error handling @@ -12,8 +11,137 @@ This is a checker wrapper lib. - testing output -#### How to: +### Quick Start +#### Examples +* [Minimal working example](https://github.com/HackerDom/Gornilo/blob/master/gornilo/examples/checker.py) +* [Checker for abstract service](https://github.com/HackerDom/Gornilo/blob/master/gornilo/examples/abstract_checker.py) + +#### How to run locally: +Start your service locally, then run checker against your service :) +```py checker.py TEST 0.0.0.0:8080``` -See `checker.py` :) -Use TEST for debugging + +### Tools and helpers +`requests_with_retries()` - wrapper for requests lib that retrys failed requests (: + +### Concepts +#### Structure +Checker has similliar functionality to e2e tests. It has three most important methods: +* `CHECK` that service works correct. +* `PUT` flag to service +* `GET` flag from service, and check that falg is correct + +If service has multiple flag types you must implement `PUT` and `GET` method for each type. + +So for service with two flag types checker must implement next methods: +``` +__CHECK__ - check all service functionality + +__PUT_1__ - put flag of firts type +__GET_1__ - check that service still has flag + +__PUT_2__ - put flag of second type +__GET_2__ - check that service still has flag +``` +Checker runs `CHECK` `PUT_i` `GET_i` sequence for each team seperatly. + +#### Verdicts +Checker methods must complete with one of next verdicts: +* `OK` - method complete without problems +* `MUMBLE` - service response incorrect +* `DOWN` - failed connect to a service +* `CHECKER_ERROR` - exceptions in checker, or brocken checker logic + +`GET` method has one more verdict +* `CORRUPT` - service has no flag or flag is incorrect + +#### Flag Rate +__Flag rate__ is an option adjusting how many flags of this type, put in the service per round. In general various flag types are implements for various vulnarabilities, that vary in complexity. With __flag rate__ you can put more flag to difficult vuln and less to easy. + +#### Flag id +__Flag id__ - all data you need to get thed flag, for example username+password. Passed from `PUT` to `GET` methods. + +#### Public flag Id +__Public flag id__ is a feature simplifying service implementation. In most cases attacker must knows ids of entities of your service. So you need implement additional handler for listing, and check this handler in __CHECK__ method. Public flag id allows you to pass such ids with verdict, then attacker can list its from checksystem api. + +### Example +Let's implement simple checker for abstract service +``` +@checker.define_check # every checker method should be wrapped with decorator checker.define_... +async def check_service(request: CheckRequest) -> Verdict: + user = 'Bob' + + url = f"http://{request.hostname}/register/{user}" #request.hostname - address of checking team's service in host:port format + + response = requests_with_retries().post(url, data = "user") + + if response.status_code != 200: #if service response is unexpected, return mumble + return Verdict.MUMBLE(f"response code {response.status_code}") #add reason to verdict, it will be shown to the checking team + + return Verdict.OK() +``` + +Next we need `PUT` + `GET` method pair, they should look like: +``` +#flag flag_id_description - hint for players, vuln_rate - flag rate +@checker.define_vuln(flag_id_description="flag_id is user", vuln_rate=1) +class FirstVuln(VulnChecker): + + #put flag method + @staticmethod + def put(request: PutRequest) -> Verdict: + ... + return Verdict.OK(...flag_id...) + + #get flag method + @staticmethod + def get(request: GetRequest) -> Verdict: + ... + return Verdict.Ok() +``` + +Implement some logic for clarity + +``` +#flag flag_id_description - hint for players, vuln_rate - flag rate +@checker.define_vuln(flag_id_description="flag_id is user", vuln_rate=1) +class FirstVuln(VulnChecker): + @staticmethod + def put(request: PutRequest) -> Verdict: + data = { + "user": "Draco", + "Password": "PureBl00d", + "gringotts_pass_phrase": request.flag, #add flag + "guilty_secret": "loves Hermion and A/D ctf" #some functional data + } + + url = f"http://{request.hostname}/secrets/{data['user']}" + + response = requests_with_retries().post(url, data = json.dumps(data)) + + if response.status_code != 200: + return Verdict.MUMBLE(f"response code {response.status_code}") + + # set public_flag_id, and flag_id + return Verdict.OK_WITH_FLAG_ID(data["user"] ,json.dumps(data)) + + @staticmethod + def get(request: GetRequest) -> Verdict: + #get flag id + expected = json.loads(request.flag_id) + + #get flag + user = expected["user"] + url = f"http://{request.hostname}/secrets/{user}/" + response = requests_with_retries().post(url, data=expected["password"]) + actual = json.loads(response.content) + + #check flag + if actual["gringotts_pass_phrase"] != request.flag: + return Verdict.CORRUPT(f"Expected falg {request.flag}, but actual {actual['secret']}") + + return Verdict.OK() +``` + +Full code can be found [here](https://github.com/HackerDom/Gornilo/blob/master/gornilo/examples/abstract_checker.py) diff --git a/examples/abstract_checker.py b/examples/abstract_checker.py new file mode 100644 index 0000000..efc4d13 --- /dev/null +++ b/examples/abstract_checker.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +from gornilo import CheckRequest, Verdict, Checker, PutRequest, GetRequest, VulnChecker +from gornilo.http_clients import requests_with_retries +import json + +checker = Checker() + +@checker.define_check # every checker method should be wrapped with decorator checker.define_... +async def check_service(request: CheckRequest) -> Verdict: + user = 'Bob' + + url = f"http://{request.hostname}/register/{user}" #request.hostname - address of checking team's service in host:port format + + response = requests_with_retries().post(url, data = "user") + + if response.status_code != 200: #if service response is unexpected, return mumble + return Verdict.MUMBLE(f"response code {response.status_code}") #add reason to verdict, it will be shown to the checking team + + return Verdict.OK() + + +#flag flag_id_description - hint for players, vuln_rate - flag rate +@checker.define_vuln(flag_id_description="flag_id is user", vuln_rate=1) +class FirstVuln(VulnChecker): + @staticmethod + def put(request: PutRequest) -> Verdict: + data = { + "user": "Draco", + "Password": "PureBl00d", + "secret": request.flag, #add flag + "guilty_secret": "loves Hermion and A/D ctf" #some functional data + } + + url = f"http://{request.hostname}/secrets/{data['user']}" + + response = requests_with_retries().post(url, data = json.dumps(data)) + + if response.status_code != 200: + return Verdict.MUMBLE(f"response code {response.status_code}") + + # set public_flag_id, and flag_id + return Verdict.OK_WITH_FLAG_ID(data["user"] ,json.dumps(data)) + + @staticmethod + def get(request: GetRequest) -> Verdict: + #get flag id + expected = json.loads(request.flag_id) + + #get flag + user = expected["user"] + url = f"http://{request.hostname}/secrets/{user}/" + response = requests_with_retries().post(url, data=expected["password"]) + actual = json.loads(response.content) + + #check flag + if actual["secret"] != request.flag: + return Verdict.CORRUPT(f"Expected falg {request.flag}, but actual {actual['secret']}") + + return Verdict.OK() + + +if __name__ == '__main__': + checker.run() \ No newline at end of file diff --git a/checker.py b/examples/checker.py similarity index 100% rename from checker.py rename to examples/checker.py From 1459373a4d3713e92e6beada257d8ba508aa12a7 Mon Sep 17 00:00:00 2001 From: "d.lukshto" Date: Sat, 10 Sep 2022 12:13:30 +0300 Subject: [PATCH 2/8] some fixes --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7eab917..5411ff8 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,13 @@ If service has multiple flag types you must implement `PUT` and `GET` method for So for service with two flag types checker must implement next methods: ``` -__CHECK__ - check all service functionality +CHECK - check all service functionality -__PUT_1__ - put flag of firts type -__GET_1__ - check that service still has flag +PUT_1 - put flag of firts type +GET_1 - check that service still has flag -__PUT_2__ - put flag of second type -__GET_2__ - check that service still has flag +PUT_2 - put flag of second type +GET_2 - check that service still has flag ``` Checker runs `CHECK` `PUT_i` `GET_i` sequence for each team seperatly. From 98b98e595680914f75b0c2b944ee66c53ea39ef5 Mon Sep 17 00:00:00 2001 From: "d.lukshto" Date: Sat, 10 Sep 2022 12:16:08 +0300 Subject: [PATCH 3/8] beatufiy --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5411ff8..d365f10 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,14 @@ Checker methods must complete with one of next verdicts: `GET` method has one more verdict * `CORRUPT` - service has no flag or flag is incorrect -#### Flag Rate -__Flag rate__ is an option adjusting how many flags of this type, put in the service per round. In general various flag types are implements for various vulnarabilities, that vary in complexity. With __flag rate__ you can put more flag to difficult vuln and less to easy. +#### Flag Rate aka Vuln Rate +Flag rate is an option adjusting how many flags of this type, put in the service per round. In general various flag types are implements for various vulnarabilities, that vary in complexity. With __flag rate__ you can put more flag to difficult vuln and less to easy. #### Flag id -__Flag id__ - all data you need to get thed flag, for example username+password. Passed from `PUT` to `GET` methods. +Flag id - all data you need to get thed flag, for example username+password. Passed from `PUT` to `GET` methods. #### Public flag Id -__Public flag id__ is a feature simplifying service implementation. In most cases attacker must knows ids of entities of your service. So you need implement additional handler for listing, and check this handler in __CHECK__ method. Public flag id allows you to pass such ids with verdict, then attacker can list its from checksystem api. +Public flag id is a feature simplifying service implementation. In most cases attacker must knows ids of entities of your service. So you need implement additional handler for listing, and check this handler in __CHECK__ method. Public flag id allows you to pass such ids with verdict, then attacker can list its from checksystem api. ### Example Let's implement simple checker for abstract service From f7463e6d53e741f74740bfe4ecdc0ee08b910ee4 Mon Sep 17 00:00:00 2001 From: "d.lukshto" Date: Sat, 10 Sep 2022 12:17:31 +0300 Subject: [PATCH 4/8] set code format --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d365f10..62455b5 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Public flag id is a feature simplifying service implementation. In most cases at ### Example Let's implement simple checker for abstract service -``` +```python @checker.define_check # every checker method should be wrapped with decorator checker.define_... async def check_service(request: CheckRequest) -> Verdict: user = 'Bob' @@ -83,7 +83,7 @@ async def check_service(request: CheckRequest) -> Verdict: ``` Next we need `PUT` + `GET` method pair, they should look like: -``` +```python #flag flag_id_description - hint for players, vuln_rate - flag rate @checker.define_vuln(flag_id_description="flag_id is user", vuln_rate=1) class FirstVuln(VulnChecker): @@ -103,7 +103,7 @@ class FirstVuln(VulnChecker): Implement some logic for clarity -``` +```python #flag flag_id_description - hint for players, vuln_rate - flag rate @checker.define_vuln(flag_id_description="flag_id is user", vuln_rate=1) class FirstVuln(VulnChecker): From 4423bf7b2ea207ca2bbbc99fdae14cb16a733e47 Mon Sep 17 00:00:00 2001 From: "d.lukshto" Date: Sat, 10 Sep 2022 12:21:19 +0300 Subject: [PATCH 5/8] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62455b5..a8363bd 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Checker methods must complete with one of next verdicts: Flag rate is an option adjusting how many flags of this type, put in the service per round. In general various flag types are implements for various vulnarabilities, that vary in complexity. With __flag rate__ you can put more flag to difficult vuln and less to easy. #### Flag id -Flag id - all data you need to get thed flag, for example username+password. Passed from `PUT` to `GET` methods. +Flag id - all data you need to get the flag, for example username+password. Passed from `PUT` to `GET` methods. #### Public flag Id Public flag id is a feature simplifying service implementation. In most cases attacker must knows ids of entities of your service. So you need implement additional handler for listing, and check this handler in __CHECK__ method. Public flag id allows you to pass such ids with verdict, then attacker can list its from checksystem api. From 0ade37c6d849806e01c2df7ec7ccab0a38a31e1d Mon Sep 17 00:00:00 2001 From: "d.lukshto" Date: Sat, 10 Sep 2022 12:22:39 +0300 Subject: [PATCH 6/8] fix type --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8363bd..f1ddb43 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Let's implement simple checker for abstract service ```python @checker.define_check # every checker method should be wrapped with decorator checker.define_... async def check_service(request: CheckRequest) -> Verdict: - user = 'Bob' + user = 'Draco' url = f"http://{request.hostname}/register/{user}" #request.hostname - address of checking team's service in host:port format From b3288b90d9bb0b2380e73113029eda6b23c899f0 Mon Sep 17 00:00:00 2001 From: "d.lukshto" Date: Sat, 10 Sep 2022 12:23:50 +0300 Subject: [PATCH 7/8] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1ddb43..6f9c9b7 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ class FirstVuln(VulnChecker): Implement some logic for clarity ```python -#flag flag_id_description - hint for players, vuln_rate - flag rate +#flag_id_description - hint for players, vuln_rate - flag rate @checker.define_vuln(flag_id_description="flag_id is user", vuln_rate=1) class FirstVuln(VulnChecker): @staticmethod From 2430e496eef85286dfc6bacb7d6eecea105b6cff Mon Sep 17 00:00:00 2001 From: "d.lukshto" Date: Sat, 10 Sep 2022 12:39:16 +0300 Subject: [PATCH 8/8] fix many many typos --- README.md | 128 +++++++++++++++++++++++++++--------------------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 6f9c9b7..1dcbbd1 100644 --- a/README.md +++ b/README.md @@ -13,17 +13,17 @@ Gornilo is a checker wrapper lib. ### Quick Start #### Examples -* [Minimal working example](https://github.com/HackerDom/Gornilo/blob/master/gornilo/examples/checker.py) +* [Minimal working example](https://github.com/HackerDom/Gornilo/blob/master/gornilo/examples/checker.py)  * [Checker for abstract service](https://github.com/HackerDom/Gornilo/blob/master/gornilo/examples/abstract_checker.py) -#### How to run locally: +#### How to run locally:  Start your service locally, then run checker against your service :) ```py checker.py TEST 0.0.0.0:8080``` ### Tools and helpers -`requests_with_retries()` - wrapper for requests lib that retrys failed requests (: - +`requests_with_retries()` - wrapper for requests lib that retries failed requests (: +  ### Concepts #### Structure @@ -32,14 +32,14 @@ Checker has similliar functionality to e2e tests. It has three most important me * `PUT` flag to service * `GET` flag from service, and check that falg is correct -If service has multiple flag types you must implement `PUT` and `GET` method for each type. +If service has multiple flag types, you must implement `PUT` and `GET` method for each type. -So for service with two flag types checker must implement next methods: +So for a service with two flag types, the checker must implement the following methods: ``` CHECK - check all service functionality PUT_1 - put flag of firts type -GET_1 - check that service still has flag +GET_1 - check that service still has a flag PUT_2 - put flag of second type GET_2 - check that service still has flag @@ -57,91 +57,91 @@ Checker methods must complete with one of next verdicts: * `CORRUPT` - service has no flag or flag is incorrect #### Flag Rate aka Vuln Rate -Flag rate is an option adjusting how many flags of this type, put in the service per round. In general various flag types are implements for various vulnarabilities, that vary in complexity. With __flag rate__ you can put more flag to difficult vuln and less to easy. +Flag rate is an option adjusting how many flags of this type are put into the service per round. In general, various flag types are implemented for various vulnerabilities that vary in complexity. With __flag rate__ you can put more flag to difficult vuln and less to easy. #### Flag id -Flag id - all data you need to get the flag, for example username+password. Passed from `PUT` to `GET` methods. +Flag id-all data you need to get the flag, for example username+password. Passed from `PUT` to `GET` methods. #### Public flag Id -Public flag id is a feature simplifying service implementation. In most cases attacker must knows ids of entities of your service. So you need implement additional handler for listing, and check this handler in __CHECK__ method. Public flag id allows you to pass such ids with verdict, then attacker can list its from checksystem api. +Public flag id is a feature that simplifies service implementation. In most cases, an attacker must know the ids of entities in your service. So you need to implement an additional handler for listing and check this handler in the `CHECK` method. Public flag id allows you to pass such ids with a verdict, then the attacker can list them from the checksystem api. ### Example Let's implement simple checker for abstract service ```python @checker.define_check # every checker method should be wrapped with decorator checker.define_... async def check_service(request: CheckRequest) -> Verdict: - user = 'Draco' +    user = 'Draco' - url = f"http://{request.hostname}/register/{user}" #request.hostname - address of checking team's service in host:port format +    url = f"http://{request.hostname}/register/{user}" #request.hostname - address of checking team's service in host:port format - response = requests_with_retries().post(url, data = "user") +    response = requests_with_retries().post(url, data = "user") - if response.status_code != 200: #if service response is unexpected, return mumble - return Verdict.MUMBLE(f"response code {response.status_code}") #add reason to verdict, it will be shown to the checking team +    if response.status_code != 200: #if service response is unexpected, return mumble +        return Verdict.MUMBLE(f"response code {response.status_code}") #add reason to verdict, it will be shown to the checking team - return Verdict.OK() +    return Verdict.OK() ``` -Next we need `PUT` + `GET` method pair, they should look like: +Next we need `PUT` + `GET` method pair. They should look like: ```python #flag flag_id_description - hint for players, vuln_rate - flag rate @checker.define_vuln(flag_id_description="flag_id is user", vuln_rate=1) class FirstVuln(VulnChecker): - #put flag method - @staticmethod - def put(request: PutRequest) -> Verdict: - ... - return Verdict.OK(...flag_id...) - - #get flag method - @staticmethod - def get(request: GetRequest) -> Verdict: - ... - return Verdict.Ok() +    #put flag method +    @staticmethod +    def put(request: PutRequest) -> Verdict: +        ... +        return Verdict.OK(...flag_id...) + +    #get flag method +    @staticmethod +    def get(request: GetRequest) -> Verdict: +        ... +        return Verdict.Ok() ``` -Implement some logic for clarity +Implement some logic for clarity. ```python #flag_id_description - hint for players, vuln_rate - flag rate @checker.define_vuln(flag_id_description="flag_id is user", vuln_rate=1) class FirstVuln(VulnChecker): - @staticmethod - def put(request: PutRequest) -> Verdict: - data = { - "user": "Draco", - "Password": "PureBl00d", - "gringotts_pass_phrase": request.flag, #add flag - "guilty_secret": "loves Hermion and A/D ctf" #some functional data - } - - url = f"http://{request.hostname}/secrets/{data['user']}" - - response = requests_with_retries().post(url, data = json.dumps(data)) - - if response.status_code != 200: - return Verdict.MUMBLE(f"response code {response.status_code}") - - # set public_flag_id, and flag_id - return Verdict.OK_WITH_FLAG_ID(data["user"] ,json.dumps(data)) - - @staticmethod - def get(request: GetRequest) -> Verdict: - #get flag id - expected = json.loads(request.flag_id) - - #get flag - user = expected["user"] - url = f"http://{request.hostname}/secrets/{user}/" - response = requests_with_retries().post(url, data=expected["password"]) - actual = json.loads(response.content) - - #check flag - if actual["gringotts_pass_phrase"] != request.flag: - return Verdict.CORRUPT(f"Expected falg {request.flag}, but actual {actual['secret']}") - - return Verdict.OK() +    @staticmethod +    def put(request: PutRequest) -> Verdict: +        data = { +            "user": "Draco", +            "Password": "PureBl00d", +            "gringotts_pass_phrase": request.flag, #add flag +            "guilty_secret": "loves Hermion and A/D ctf" #some functional data +        } + +        url = f"http://{request.hostname}/secrets/{data['user']}" + +        response = requests_with_retries().post(url, data = json.dumps(data)) + +        if response.status_code != 200: +            return Verdict.MUMBLE(f"response code {response.status_code}") +         +        # set public_flag_id, and flag_id +        return Verdict.OK_WITH_FLAG_ID(data["user"] ,json.dumps(data))   + +    @staticmethod +    def get(request: GetRequest) -> Verdict: +        #get flag id +        expected = json.loads(request.flag_id)  + +        #get flag +        user = expected["user"] +        url = f"http://{request.hostname}/secrets/{user}/" +        response = requests_with_retries().post(url, data=expected["password"]) +        actual = json.loads(response.content) + +        #check flag +        if actual["gringotts_pass_phrase"] != request.flag:  +            return Verdict.CORRUPT(f"Expected falg {request.flag}, but actual {actual['secret']}") + +        return Verdict.OK() ``` Full code can be found [here](https://github.com/HackerDom/Gornilo/blob/master/gornilo/examples/abstract_checker.py)