Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add dock and samples #18

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 133 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use relative pathes instead of absolute:
[Minimal working example](examples/checker.py)

Same for abstract_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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: falg -> flag


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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: firts -> first

__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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: brocken -> broken


`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)
63 changes: 63 additions & 0 deletions examples/abstract_checker.py
Original file line number Diff line number Diff line change
@@ -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()
File renamed without changes.