Skip to content

Commit baa55cb

Browse files
Merge pull request #4 from C4T-BuT-S4D/jn/doc
DOCS: MVP
2 parents 9e5da81 + 086aa9d commit baa55cb

20 files changed

+1054
-0
lines changed

checkers/docs/checker.py

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/usr/bin/env python3
2+
import random
3+
import re
4+
import string
5+
import sys
6+
7+
import checklib
8+
import requests
9+
from checklib import *
10+
from checklib import status
11+
12+
import docs_lib
13+
14+
LEVEL_1_DOMAINS = [
15+
".AC", ".AD", ".AE", ".AERO", ".AF", ".AG", ".AI", ".AL", ".AM", ".AN", ".AO", ".AQ", ".AR", ".ARPA", ".AS", ".ASIA",
16+
".AT", ".AU", ".AW", ".AX", ".AZ", ".BA", ".BB", ".BD", ".BE", ".BF", ".BG", ".BH", ".BI", ".BIZ", ".BJ", ".BL", ".BM",
17+
".BN", ".BO", ".BR", ".BS", ".BT", ".BV", ".BW", ".BY", ".BZ", ".CA", ".CAT", ".CC", ".CD", ".CF", ".CG", ".CH", ".CI",
18+
".CK", ".CL", ".CM", ".CN", ".CO", ".COM", ".COOP", ".CR", ".CU", ".CV", ".CX", ".CY", ".CZ", ".DE", ".DJ", ".DK", ".DM",
19+
".DO", ".DZ", ".EC", ".EDU", ".EE", ".EG", ".EH", ".ER", ".ES", ".ET", ".EU", ".FI", ".FJ", ".FK", ".FM", ".FO", ".FR",
20+
".GA", ".GB", ".GD", ".GE", ".GF", ".GG", ".GH", ".GI", ".GL", ".GM", ".GN", ".GOV", ".GP", ".GQ", ".GR", ".GS", ".GT",
21+
".GU", ".GW", ".GY", ".HK", ".HM", ".HN", ".HR", ".HT", ".HU", ".ID", ".IE", ".IL", ".IM", ".IN", ".INFO", ".INT", ".IO",
22+
".IQ", ".IR", ".IS", ".IT", ".JE", ".JM", ".JO", ".JOBS", ".JP", ".KE", ".KG", ".KH", ".KI", ".KM", ".KN", ".KP", ".KR",
23+
".KW", ".KY", ".KZ", ".LA", ".LB", ".LC", ".LI", ".LK", ".LR", ".LS", ".LT", ".LU", ".LV", ".LY", ".MA", ".MC", ".MD",
24+
".ME", ".MF", ".MG", ".MH", ".MIL", ".MK", ".ML", ".MM", ".MN", ".MO", ".MOBI", ".MP", ".MQ", ".MR", ".MS", ".MT", ".MU",
25+
".MUSEUM", ".MV", ".MW", ".MX", ".MY", ".MZ", ".NA", ".NAME", ".NC", ".NE", ".NET", ".NF", ".NG", ".NI", ".NL", ".NO",
26+
".NP", ".NR", ".NU", ".NZ", ".OM", ".ORG", ".PA", ".PE", ".PF", ".PG", ".PH", ".PK", ".PL", ".PM", ".PN", ".PR", ".PRO",
27+
".PS", ".PT", ".PW", ".PY", ".QA", ".RE", ".RO", ".RS", ".RU", ".RW", ".SA", ".SB", ".SC", ".SD", ".SE", ".SG", ".SH",
28+
".SI", ".SJ", ".SK", ".SL", ".SM", ".SN", ".SO", ".SR", ".ST", ".SU", ".SV", ".SY", ".SZ", ".TC", ".TD", ".TEL", ".TF",
29+
".TG", ".TH", ".TJ", ".TK", ".TL", ".TM", ".TN", ".TO", ".TP", ".TR", ".TRAVEL", ".TT", ".TV", ".TW", ".TZ", ".UA", ".UG",
30+
".UK", ".UM", ".US", ".UY", ".UZ", ".VA", ".VC", ".VE", ".VG", ".VI", ".VN", ".VU", ".WF", ".WS"
31+
]
32+
33+
34+
class Checker(BaseChecker):
35+
vulns: int = 1
36+
timeout: int = 15
37+
uses_attack_data: bool = True
38+
39+
def __init__(self, *args, **kwargs):
40+
super(Checker, self).__init__(*args, **kwargs)
41+
self.lib = docs_lib.DocsLib(self)
42+
self.token_regexp = re.compile(r'^[0-9A-Za-z]{1,80}$')
43+
44+
def get_random_org(self):
45+
l = rnd_string(10, alphabet=string.ascii_lowercase)
46+
r = random.choice(LEVEL_1_DOMAINS)
47+
return f"{l}{r}".lower()
48+
49+
def action(self, action, *args, **kwargs):
50+
try:
51+
super(Checker, self).action(action, *args, **kwargs)
52+
except requests.exceptions.ConnectionError:
53+
self.cquit(Status.DOWN, 'Connection error', 'Got requests connection error')
54+
55+
def check(self):
56+
session = checklib.get_initialized_session()
57+
org = self.get_random_org()
58+
59+
response = self.lib.create_org(session, org)
60+
token = response.get('token')
61+
org_id = response.get('id')
62+
63+
self.assert_eq(bool(self.token_regexp.fullmatch(token)), True, 'Invalid token format')
64+
65+
u, p = rnd_username(), rnd_password()
66+
u1, p1 = rnd_username(), rnd_password()
67+
self.lib.create_user(session, u, p, token)
68+
u = f'{u}@{org}'
69+
70+
session = self.lib.login(session, u, p)
71+
72+
title = rnd_string(10)
73+
content = rnd_string(10)
74+
75+
got_doc = self.lib.create_doc(session, title, content)
76+
got_doc = self.lib.get_doc(session, got_doc.get('id'))
77+
78+
self.lib.create_user(session, u1, p1, token)
79+
u1 = f'{u1}@{org}'
80+
81+
session_alter = checklib.get_initialized_session()
82+
self.lib.login(session_alter, u1, p1)
83+
84+
got_alter_doc = self.lib.get_doc(session_alter, got_doc.get('id'))
85+
self.assert_eq(got_alter_doc.get('title'), title, 'Failed to get document')
86+
self.assert_eq(got_alter_doc.get('content'), content, 'Failed to get document')
87+
88+
new_title = rnd_string(10)
89+
self.lib.update_doc(session, got_doc.get('id'), title=new_title)
90+
91+
got_updated_doc = self.lib.get_doc(session, got_doc.get('id'))
92+
self.assert_eq(got_updated_doc.get('title'), new_title, 'Failed to update document')
93+
self.assert_eq(got_updated_doc.get('content'), content, 'Failed to update document')
94+
95+
search_results = self.lib.search(session_alter, new_title)
96+
self.assert_in(got_updated_doc.get('id'), [x.get('id') for x in search_results], 'Failed to search document')
97+
self.assert_in(got_updated_doc.get('title'), [x.get('title') for x in search_results],
98+
'Failed to search document')
99+
self.assert_in(got_updated_doc.get('content'), [x.get('content') for x in search_results],
100+
'Failed to search document')
101+
102+
self.cquit(Status.OK)
103+
104+
def put(self, flag_id: str, flag: str, vuln: str):
105+
session = checklib.get_initialized_session()
106+
org = self.get_random_org()
107+
108+
response = self.lib.create_org(session, org)
109+
token = response.get('token')
110+
org_id = response.get('id')
111+
112+
self.assert_eq(bool(self.token_regexp.fullmatch(token)), True, 'Invalid token format')
113+
self.assert_eq(bool(self.token_regexp.fullmatch(org_id)), True, 'Invalid org_id format')
114+
115+
116+
u, p = rnd_username(), rnd_password()
117+
self.lib.create_user(session, u, p, token)
118+
119+
sess = checklib.get_initialized_session()
120+
u = f'{u}@{org}'
121+
self.lib.login(sess, u, p)
122+
title = checklib.rnd_string(10)
123+
created_doc = self.lib.create_doc(sess, title, flag)
124+
125+
doc_id = created_doc.get('id')
126+
self.assert_eq(bool(self.token_regexp.fullmatch(doc_id)), True, 'Invalid docid format')
127+
128+
self.cquit(Status.OK, f'{org}:{org_id}:{doc_id}', f"{token}:{u}:{p}:{doc_id}")
129+
130+
def get(self, flag_id: str, flag: str, vuln: str):
131+
token, u, p, doc_id = flag_id.split(':')
132+
sess = checklib.get_initialized_session()
133+
self.lib.login(sess, u, p, status=status.Status.CORRUPT)
134+
doc = self.lib.get_doc(sess, doc_id, status=status.Status.CORRUPT)
135+
self.assert_eq(doc.get('content'), flag, 'Invalid content', status=status.Status.CORRUPT)
136+
137+
sess = checklib.get_initialized_session()
138+
u1, p1 = rnd_username(), rnd_password()
139+
created_user = self.lib.create_user(sess, u1, p1, token)
140+
141+
sess = checklib.get_initialized_session()
142+
self.lib.login(sess, created_user.get('email'), created_user.get('password'))
143+
144+
self.lib.search(sess, '', status=status.Status.CORRUPT)
145+
146+
self.cquit(Status.OK)
147+
148+
149+
if __name__ == '__main__':
150+
c = Checker(sys.argv[2])
151+
152+
try:
153+
c.action(sys.argv[1], *sys.argv[3:])
154+
except c.get_check_finished_exception() as e:
155+
cquit(status.Status(c.status), c.public, c.private)

checkers/docs/docs_lib.py

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from typing import Optional
2+
3+
import checklib
4+
from checklib import BaseChecker
5+
import requests
6+
7+
PORT = 8000
8+
9+
10+
class DocsLib:
11+
@property
12+
def api_url(self):
13+
return f'http://{self.host}:{self.port}/api'
14+
15+
def __init__(self, checker: BaseChecker, port=PORT, host=None):
16+
self.c = checker
17+
self.port = port
18+
self.host = host or self.c.host
19+
20+
def create_org(self, session: requests.Session, domain: str):
21+
document = {
22+
"domain": domain,
23+
}
24+
25+
resp = session.post(
26+
f"{self.api_url}/organizations",
27+
json=document
28+
)
29+
self.c.assert_eq(resp.status_code, 200, 'Failed to create organization')
30+
response_data = self.c.get_json(resp, 'Failed to create organization: invalid JSON')
31+
self.c.assert_eq(type(response_data), dict, 'Failed to create organization: invalid JSON')
32+
return response_data
33+
34+
35+
def list_orgs(self, session: requests.Session):
36+
resp = session.get(
37+
f"{self.api_url}/organizations"
38+
)
39+
self.c.assert_eq(resp.status_code, 200, 'Failed to list organization')
40+
return self.c.get_json(resp, 'Failed to list organization: invalid JSON')
41+
42+
def create_user(self, session: requests.Session, username: str, password: str, token: str):
43+
document = {
44+
"username": username,
45+
"password": password,
46+
"token": token
47+
}
48+
resp = session.post(
49+
f"{self.api_url}/users",
50+
json=document
51+
)
52+
self.c.assert_eq(resp.status_code, 200, 'Failed to create user')
53+
return self.c.get_json(resp, 'Failed to create user: invalid JSON')
54+
55+
def login(self, session: requests.Session, username: str, password: str, status: checklib.Status = checklib.Status.MUMBLE):
56+
document = {
57+
"email": username,
58+
"password": password
59+
}
60+
61+
response = session.post(
62+
f"{self.api_url}/login",
63+
json=document
64+
)
65+
self.c.assert_eq(response.status_code, 200, 'Failed to login', status=status)
66+
resp_json = self.c.get_json(response, 'Failed to login: invalid JSON', status=status)
67+
self.c.assert_eq(type(resp_json), dict, 'Failed to login: invalid JSON', status=status)
68+
token = resp_json.get('token') or ''
69+
session.headers['Authorization'] = f"Bearer {token}"
70+
return session
71+
72+
def get_user(self, session: requests.Session, status: checklib.Status = checklib.Status.MUMBLE):
73+
response = session.get(
74+
f"{self.api_url}/users/me",
75+
76+
)
77+
self.c.assert_eq(response.status_code, 200, 'Failed to get user', status=status)
78+
return self.c.get_json(response, 'Failed to get user: invalid JSON', status=status)
79+
80+
81+
def create_doc(self, session: requests.Session, title: str, content: str, status: checklib.Status = checklib.Status.MUMBLE):
82+
document = {
83+
"title": title,
84+
"content": content
85+
}
86+
87+
response = session.post(
88+
f"{self.api_url}/documents",
89+
json=document
90+
)
91+
self.c.assert_eq(response.status_code, 200, 'Failed to create document', status=status)
92+
return self.c.get_json(response, 'Failed to create document: invalid JSON', status=status)
93+
94+
def update_doc(self, session: requests.Session, doc_id:str, title: str | None, content: str | None = None,
95+
status: checklib.Status = checklib.Status.MUMBLE):
96+
document = {}
97+
if title:
98+
document['title'] = title
99+
if content:
100+
document['content'] = content
101+
102+
response = session.patch(
103+
f"{self.api_url}/documents/{doc_id}",
104+
json=document
105+
)
106+
self.c.assert_eq(response.status_code, 200, 'Failed to update document', status=status)
107+
return self.c.get_json(response, 'Failed to create document: invalid JSON', status=status)
108+
109+
def get_doc(self, session: requests.Session, doc_id: str, status: checklib.Status = checklib.Status.MUMBLE):
110+
response = session.get(
111+
f"{self.api_url}/documents/{doc_id}"
112+
)
113+
self.c.assert_eq(response.status_code, 200, 'Failed to get document', status=status)
114+
return self.c.get_json(response, 'Failed to get document: invalid JSON', status=status)
115+
116+
def delete_doc(self, session: requests.Session, doc_id: str, status: checklib.Status = checklib.Status.MUMBLE):
117+
response = session.delete(
118+
f"{self.api_url}/documents/{doc_id}"
119+
)
120+
self.c.assert_eq(response.status_code, 200, 'Failed to delete document', status=status)
121+
122+
def search(self, session: requests.Session, query: str, status: checklib.Status = checklib.Status.MUMBLE):
123+
response = session.get(f"{self.api_url}/documents",
124+
params={'query': query}
125+
)
126+
self.c.assert_eq(response.status_code, 200, 'Failed to search', status=status)
127+
return self.c.get_json(response, 'Failed to search: invalid JSON', status=status)
128+
129+
130+
def document_get_txt(self, session: requests.Session, doc_id: str, status: checklib.Status = checklib.Status.MUMBLE):
131+
response = session.get(
132+
f"{self.api_url}/document/{doc_id}/text"
133+
)
134+
self.c.assert_eq(response.status_code, 200, 'Failed to get txt', status=status)
135+
return response.text
136+
137+
138+
139+

services/docs/api/.dockerignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
venv

services/docs/api/Dockerfile

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM python:3.12-slim-bullseye
2+
3+
RUN apt update && apt install -y xxd
4+
WORKDIR /app
5+
ADD src/requirements.txt requirements.txt
6+
7+
RUN pip3 install -r requirements.txt
8+
9+
COPY app.env app.env
10+
11+
RUN sed -i "s/JWT_KEY=.*/JWT_KEY=$(xxd -u -l 20 -p /dev/urandom)/g" app.env
12+
13+
COPY src .
14+
15+
CMD fastapi run app/app.py

services/docs/api/app.env

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
JWT_KEY=secret
2+
MONGO_URI=mongodb://mongodb:27017/docs
3+
SEARCH_HOST=http://search:8080

services/docs/api/src/app/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)