adding validated services? patching forcad_local.py

This commit is contained in:
Your Name
2026-08-13 08:32:35 +07:00
parent d485f61169
commit e795614e45
1459 changed files with 420036 additions and 436 deletions

View File

@@ -0,0 +1,370 @@
#!/usr/bin/env python3
import sys
import json
import time
import random
import requests
from typing import Optional
from checklib import *
from jform_lib import CheckMachine
class Checker(BaseChecker):
vulns: int = 1
timeout: int = 15
uses_attack_data: bool = True
def __init__(self, host: str):
super().__init__(host)
self.m = CheckMachine(self)
def _new_session(self) -> requests.Session:
return get_initialized_session()
def _require_json(self, r: requests.Response, where: str) -> dict:
self.assert_in("application/json", r.headers.get("Content-Type", ""), f"{where}: Content-Type is not JSON")
return self.get_json(r, f"{where}: malformed JSON")
def _extract_form_id(self, obj: dict) -> str:
form_id = obj.get("id") or obj.get("formId") or obj.get("_id")
self.assert_neq(None, form_id, "Service returned empty form id")
form_id = str(form_id)
self.assert_neq("", form_id, "Service returned empty form id")
return form_id
def _register_and_login(self, s: requests.Session):
username = rnd_username()
password = rnd_password()
r = self.m.register(s, username, password)
self.assert_eq(200, r.status_code, "Service /signup returned unexpected status code")
_ = self._require_json(r, "/signup")
r = self.m.login(s, username, password)
self.assert_eq(200, r.status_code, "Service /login returned unexpected status code")
_ = self._require_json(r, "/login")
r = self.m.me(s)
self.assert_eq(200, r.status_code, "Service /me returned unexpected status code")
me = self._require_json(r, "/me")
self.assert_in("username", me, "Service /me missing username")
self.assert_eq(me["username"], username, "Service /me returned wrong username")
self.assert_in("userId", me, "Service /me missing userId")
user_id = str(me["userId"])
self.assert_neq("", user_id, "Service /me returned empty userId")
return username, password, user_id
def _form_catalog(self):
return [
{
"title": "Обратная связь",
"description": "Короткий опрос о качестве сервиса",
"fields": [
{"name": "email_1", "type": "email", "label": "Email", "placeholder": "you@example.ru", "required": True},
{"name": "number_1", "type": "number", "label": "Оценка (1-10)", "placeholder": "10", "required": True},
{"name": "textarea_1", "type": "textarea", "label": "Комментарий", "placeholder": "Напишите пару слов", "required": False},
],
},
{
"title": "Заявка на работу",
"description": "Расскажите о себе",
"fields": [
{"name": "text_1", "type": "text", "label": "ФИО", "placeholder": "Иванов Иван", "required": True},
{"name": "email_1", "type": "email", "label": "Почта", "placeholder": "you@example.ru", "required": True},
{"name": "number_1", "type": "number", "label": "Опыт (лет)", "placeholder": "3", "required": True},
{"name": "textarea_1", "type": "textarea", "label": "Резюме (кратко)", "placeholder": "Ключевые навыки...", "required": False},
],
},
{
"title": "Регистрация на событие",
"description": "Подтвердите участие",
"fields": [
{"name": "text_1", "type": "text", "label": "Имя", "placeholder": "Ваше имя", "required": True},
{"name": "email_1", "type": "email", "label": "Email", "placeholder": "you@example.ru", "required": True},
{"name": "number_1", "type": "number", "label": "Количество гостей", "placeholder": "0", "required": False},
{"name": "text_2", "type": "text", "label": "Дата", "placeholder": "2025-10-05", "required": True},
],
},
{
"title": "Сообщить об ошибке",
"description": "Помогите нам стать лучше",
"fields": [
{"name": "text_1", "type": "text", "label": "Заголовок", "placeholder": "Коротко о проблеме", "required": True},
{"name": "select_1", "type": "select", "label": "Серьёзность", "options": ["Низкая", "Средняя", "Высокая"], "required": True},
{"name": "textarea_1", "type": "textarea", "label": "Шаги воспроизведения", "placeholder": "1) ... 2) ...", "required": True},
],
},
{
"title": "Заказ пиццы",
"description": "Соберите свою пиццу",
"fields": [
{"name": "select_1", "type": "select", "label": "Размер", "options": ["Маленькая", "Средняя", "Большая"], "required": True},
{"name": "text_1", "type": "text", "label": "Адрес", "placeholder": "Город, улица, дом", "required": True},
{"name": "select_2", "type": "select", "label": "Соус", "options": ["Томатный", "Сливочный", "Барбекю"], "required": False},
],
},
{
"title": "Опрос о цветах",
"description": "Пара вопросов о вкусе",
"fields": [
{"name": "number_1", "type": "number", "label": "Возраст", "placeholder": "25", "required": False},
{"name": "text_1", "type": "text", "label": "Любимый цвет", "placeholder": "синий", "required": True},
],
},
{
"title": "Мини-викторина",
"description": "Два коротких вопроса",
"fields": [
{"name": "text_1", "type": "text", "label": "Имя", "placeholder": "Ваше имя", "required": True},
{"name": "number_1", "type": "number", "label": "Сколько будет 2+2?", "placeholder": "4", "required": True},
{"name": "number_2", "type": "number", "label": "Сколько дней в неделе?", "placeholder": "7", "required": True},
],
},
{
"title": "Заявка в поддержку",
"description": "Опишите проблему",
"fields": [
{"name": "email_1", "type": "email", "label": "Email", "placeholder": "you@example.ru", "required": True},
{"name": "text_1", "type": "text", "label": "Тема", "placeholder": "Коротко", "required": True},
{"name": "textarea_1", "type": "textarea", "label": "Описание", "placeholder": "Что произошло?", "required": True},
],
},
{
"title": "Командировка",
"description": "Заявка на поездку",
"fields": [
{"name": "text_1", "type": "text", "label": "Откуда", "placeholder": "Москва", "required": True},
{"name": "text_2", "type": "text", "label": "Куда", "placeholder": "Санкт-Петербург", "required": True},
{"name": "text_3", "type": "text", "label": "Дата выезда", "placeholder": "2025-10-05", "required": True},
{"name": "text_4", "type": "text", "label": "Дата возврата", "placeholder": "2025-10-07", "required": False},
{"name": "number_1", "type": "number", "label": "Бюджет, ₽", "placeholder": "15000", "required": False},
],
},
{
"title": "Рецензия на книгу",
"description": "Поделитесь впечатлениями",
"fields": [
{"name": "text_1", "type": "text", "label": "Название", "placeholder": "Книга", "required": True},
{"name": "text_2", "type": "text", "label": "Автор", "placeholder": "Автор", "required": True},
{"name": "number_1", "type": "number", "label": "Оценка (1-10)", "placeholder": "8", "required": True},
{"name": "textarea_1", "type": "textarea", "label": "Отзыв", "placeholder": "Ваши мысли...", "required": False},
],
},
]
def _pick_schema(self, seed: int):
rng = random.Random(seed)
base = self._form_catalog()[rng.randrange(10)]
schema = {
"title": base["title"],
"description": base.get("description", ""),
"fields": [dict(f) for f in base["fields"]],
}
return schema, rng
def _make_flag_sentence(self, flag: str, rng: random.Random) -> str:
templates = [
"Сегодняшний код дня — {flag} — случайно появился в заметках.",
"Если что, подсказка спрятана так: {flag}.",
"Запомни шифр: {flag}, пригодится позже.",
"Пока никто не видит, оставлю тут {flag}.",
"Лозунг встречи: \"{flag}\" — без комментариев.",
"Пароль на вход: {flag}; не перепутай!",
"Метка для отчёта — {flag} — проверь внимательно.",
"Секретный ингредиент рецепта: {flag}.",
"В списке дел первым пунктом стоит {flag}.",
"На полях тетради выведено: {flag}.",
]
return rng.choice(templates).format(flag=flag)
def _make_valid_payload(self, schema: dict, username: str, rng: random.Random, flag: Optional[str] = None) -> dict:
def word(n=8):
letters = "абвгдеёжзийклмнопрстуфхцчшщэюя"
return "".join(rng.choice(letters) for _ in range(n))
email = f"{username}@example.ru"
text_like = [f for f in schema.get("fields", []) if f.get("type") in ("text", "textarea")]
flag_field_name = rng.choice(text_like)["name"] if (flag and text_like) else None
sentence = self._make_flag_sentence(flag, rng) if flag else None
payload = {}
for f in schema.get("fields", []):
t = f.get("type")
name = f.get("name")
req = bool(f.get("required"))
if name == flag_field_name and sentence is not None:
payload[name] = sentence
continue
if t == "email":
payload[name] = email
elif t == "number":
payload[name] = rng.randint(1, 100)
elif t == "textarea":
payload[name] = f"{word(6)} {word(6)} {word(6)} {word(6)}"
elif t == "select":
options = f.get("options") or []
payload[name] = rng.choice(options) if options else "Вариант 1"
else:
payload[name] = word(8) if req else word(5)
return payload
def check(self):
try:
s1 = self._new_session()
u1, p1, id1 = self._register_and_login(s1)
s2 = self._new_session()
u2, p2, id2 = self._register_and_login(s2)
try:
id1_i = int(id1); id2_i = int(id2)
except Exception as e:
self.cquit(Status.MUMBLE, "Service returned non-integer userId", str(e))
self.assert_eq(True, id2_i > id1_i, "Service produced incorrect userId ordering")
schema1, rng = self._pick_schema(seed=id2_i)
schema2, _ = self._pick_schema(seed=id2_i + 777)
r = self.m.create_form(s2, schema1)
self.assert_eq(200, r.status_code, "Service create_form returned unexpected status code")
created1 = self._require_json(r, "create_form #1")
form_id1 = self._extract_form_id(created1)
self.assert_in("formNumber", created1, "Service did not return formNumber on creation")
num1 = int(created1["formNumber"])
r = self.m.get_form(s2, form_id1)
self.assert_eq(200, r.status_code, "Service get_form returned unexpected status code")
gf1 = self._require_json(r, "get_form #1")
self.assert_eq(form_id1, str(gf1.get("id") or gf1.get("formId") or gf1.get("_id") or ""), "Service get_form returned wrong form id")
self.assert_eq(num1, int(gf1.get("formNumber")), "Service get_form returned wrong formNumber")
r = self.m.create_form(s2, schema2)
self.assert_eq(200, r.status_code, "Service create_form returned unexpected status code")
created2 = self._require_json(r, "create_form #2")
form_id2 = self._extract_form_id(created2)
self.assert_in("formNumber", created2, "Service did not return formNumber on creation")
num2 = int(created2["formNumber"])
self.assert_eq(True, num2 > num1, "Service produced non-increasing formNumber")
self.assert_neq(form_id1, form_id2, "Service assigned duplicate form identifiers")
r = self.m.get_my_forms(s2)
self.assert_eq(200, r.status_code, "Service get_my_forms returned unexpected status code")
my = self._require_json(r, "get_my_forms")
self.assert_eq(True, isinstance(my, list), "Service returned invalid forms list")
ids_in_list = {str(i.get("id") or i.get("formId") or i.get("_id") or "") for i in my if isinstance(i, dict)}
self.assert_in(form_id1, ids_in_list, "Service did not list previously created form #1")
self.assert_in(form_id2, ids_in_list, "Service did not list previously created form #2")
payload = self._make_valid_payload(schema2, username=u2, rng=random.Random(id2_i))
r = self.m.submit_form_response(s2, form_id2, payload)
self.assert_eq(200, r.status_code, "Service submit_form_response returned unexpected status code")
self._require_json(r, "submit_form_response")
r = self.m.get_form_results(s2, form_id2)
self.assert_eq(200, r.status_code, "Service get_form_results returned unexpected status code")
res_after = self._require_json(r, "get_form_results after submit")
self.assert_eq(True, isinstance(res_after, list), "Service returned invalid results list")
self.assert_eq(True, len(res_after) >= 1, "Service did not persist submitted response")
self.cquit(Status.OK)
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
self.cquit(Status.DOWN, "Service is down or unreachable", str(e))
def put(self, flag_id: str, flag: str, vuln: str):
try:
s = self._new_session()
username, password, user_id = self._register_and_login(s)
user_id_int = int(user_id)
schema, rng = self._pick_schema(seed=user_id_int)
r = self.m.create_form(s, schema)
self.assert_eq(200, r.status_code, "Service create_form returned unexpected status code")
created = self._require_json(r, "create_form")
form_id = self._extract_form_id(created)
self.assert_in("formNumber", created, "Service did not return formNumber on creation")
_ = int(created["formNumber"])
payload = self._make_valid_payload(schema, username=username, rng=rng, flag=flag)
r = self.m.submit_form_response(s, form_id, payload)
self.assert_eq(200, r.status_code, "Service submit_form_response returned unexpected status code")
_ = self._require_json(r, "submit_form_response")
public = json.dumps({"userId": user_id, "username": username})
private = json.dumps({"username": username, "password": password, "form_id": form_id})
self.cquit(Status.OK, public, private)
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
self.cquit(Status.DOWN, "Service is down or unreachable", str(e))
def get(self, flag_id: str, flag: str, vuln: str):
try:
s = self._new_session()
try:
blob = json.loads(flag_id)
except Exception as e:
self.cquit(Status.CORRUPT, "Invalid private data", str(e))
if isinstance(blob, dict) and {"username", "password", "form_id"} <= set(blob.keys()):
priv = blob
elif isinstance(blob, dict) and isinstance(blob.get("private"), dict):
priv = blob["private"]
else:
self.cquit(Status.CORRUPT, "Missing auth and form_id fields", f"got: {sorted(blob.keys()) if isinstance(blob, dict) else type(blob)}")
username = priv.get("username")
password = priv.get("password")
form_id = str(priv.get("form_id") or "")
self.assert_eq(True, all([username, password, form_id]), "Missing required fields to access form")
r = self.m.login(s, username, password)
self.assert_eq(200, r.status_code, "Service /login returned unexpected status code")
_ = self._require_json(r, "/login (GET)")
r = self.m.get_form_results(s, form_id)
self.assert_eq(200, r.status_code, "Service get_form_results returned unexpected status code")
res = self._require_json(r, "get_form_results (GET)")
self.assert_eq(True, isinstance(res, list), "Service returned invalid results list")
def contains_flag(obj) -> bool:
if isinstance(obj, str):
if flag in obj:
return True
try:
inner = json.loads(obj)
except Exception:
return False
return contains_flag(inner)
if isinstance(obj, dict):
for k in ("response", "data", "payload", "answers"):
if k in obj and contains_flag(obj[k]):
return True
for v in obj.values():
if contains_flag(v):
return True
return False
if isinstance(obj, list):
for v in obj:
if contains_flag(v):
return True
return False
return False
found = any(contains_flag(item) for item in res)
self.assert_eq(True, found, "Service does not return previously submitted data (flag not found)")
self.cquit(Status.OK)
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
self.cquit(Status.DOWN, "Service is down or unreachable", str(e))
if __name__ == '__main__':
c = Checker(sys.argv[2])
try:
c.action(sys.argv[1], *sys.argv[3:])
except c.get_check_finished_exception():
cquit(Status(c.status), c.public, c.private)

View File

@@ -0,0 +1,54 @@
import requests
import json
from checklib import *
PORT = 5003
class CheckMachine:
@property
def url(self):
return f"http://{self.c.host}:{self.port}"
def __init__(self, checker: BaseChecker):
self.port = PORT
self.c = checker
def register(self, session: requests.Session, username: str, password: str):
data = {"username": username, "password": password}
headers = {"Content-Type": "application/json"}
return session.post(f"{self.url}/api/account/signup", data=json.dumps(data), headers=headers)
def login(self, session: requests.Session, username: str, password: str):
data = {"username": username, "password": password}
headers = {"Content-Type": "application/json"}
return session.post(f"{self.url}/api/account/login", data=json.dumps(data), headers=headers)
def me(self, session: requests.Session):
return session.get(f"{self.url}/api/account/me")
def logout(self, session: requests.Session):
headers = {"Content-Type": "application/json"}
return session.post(f"{self.url}/api/account/logout", data=json.dumps({}), headers=headers)
def create_form(self, session: requests.Session, schema: dict):
data = {"schema": schema}
headers = {"Content-Type": "application/json"}
return session.post(f"{self.url}/api/form/new", data=json.dumps(data), headers=headers)
def get_form(self, session: requests.Session, form_id: str):
return session.get(f"{self.url}/api/form/{form_id}")
def update_form(self, session: requests.Session, form_id: str, schema: dict):
data = {"schema": schema}
headers = {"Content-Type": "application/json"}
return session.put(f"{self.url}/api/form/{form_id}/update", data=json.dumps(data), headers=headers)
def submit_form_response(self, session: requests.Session, form_id: str, payload: dict):
headers = {"Content-Type": "application/json"}
return session.post(f"{self.url}/api/form/{form_id}/submit", data=json.dumps(payload), headers=headers)
def get_my_forms(self, session: requests.Session):
return session.get(f"{self.url}/api/form/my-forms")
def get_form_results(self, session: requests.Session, form_id: str):
return session.get(f"{self.url}/api/form/{form_id}/results")