Init. Commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<h1 align="center">SealTail</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/category-Crypto-blueviolet" alt="Crypto"/>
|
||||
<img src="https://img.shields.io/badge/difficulty-easy-brightgreen" alt="easy"/>
|
||||
</p>
|
||||
|
||||
Релизная очередь принимает `release.capsule`: заголовок, RSA-подпись и команды в TLV-формате. В заголовке есть `signed_len` — длина подписанной части. Проверяющий использует именно её, а очередь после успешной проверки разбирает весь payload до конца файла.
|
||||
|
||||
## Решение
|
||||
|
||||
Сначала прогоняем исходную капсулу через публичный verifier:
|
||||
|
||||
```bash
|
||||
python3 public/verify_capsule.py public/release.capsule
|
||||
```
|
||||
|
||||
Он показывает `signature_valid=True`, `payload_len=181`, `signed_len=181`, `unsigned_tail_len=0`. Пока подпись покрывает весь payload, неподписанного хвоста нет.
|
||||
|
||||
Открываем `FORMAT.md`. Для экспорта карантинного ключа предусмотрен тег `0x42 EXPORT_QUARANTINE_KEY`. TLV-запись состоит из байта тега, двух байт длины в big-endian и значения. Дописываем такую команду в конец капсулы:
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
value = (b"lane=quarantine\nrequestor=release-audit\n"
|
||||
b"reason=post-signature-tail-check\n")
|
||||
tail = bytes([0x42]) + len(value).to_bytes(2, "big") + value
|
||||
original = Path("public/release.capsule").read_bytes()
|
||||
Path("modified.capsule").write_bytes(original + tail)
|
||||
```
|
||||
|
||||
Подписанные байты и `signed_len` остались прежними. Проверяем модифицированный файл той же утилитой. Значение `unsigned_tail_len` должно стать больше нуля, а `signature_valid` остаться `True`.
|
||||
|
||||
```bash
|
||||
python3 public/verify_capsule.py modified.capsule
|
||||
```
|
||||
|
||||
Отправляем капсулу в очередь:
|
||||
|
||||
```bash
|
||||
curl -sS --data-binary @modified.capsule \
|
||||
-H 'Content-Type: application/octet-stream' \
|
||||
http://<host>:31337/submit
|
||||
```
|
||||
|
||||
RSA-проверка принимает исходный префикс, затем TLV-парсер доходит до добавленной неподписанной команды и возвращает ключ с флагом.
|
||||
|
||||
> Подпись подтверждает только те байты, которые в неё включили. Если после проверки исполнять ещё и неподписанный хвост, целостность всего релиза она уже не гарантирует.
|
||||
|
||||
[Солвер](solve/solve.py).
|
||||
|
||||
## Флаг
|
||||
|
||||
`caplag{signed_prefix_is_not_a_release}`
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import urllib.request
|
||||
|
||||
|
||||
ACTION_EXPORT_KEY = 0x42
|
||||
|
||||
|
||||
def encode_tlv(tag: int, value: bytes) -> bytes:
|
||||
return bytes([tag]) + len(value).to_bytes(2, "big") + value
|
||||
|
||||
|
||||
def build_modified_capsule(input_path: pathlib.Path, output_path: pathlib.Path) -> bytes:
|
||||
capsule = input_path.read_bytes()
|
||||
tail = encode_tlv(
|
||||
ACTION_EXPORT_KEY,
|
||||
b"lane=quarantine\nrequestor=release-audit\nreason=post-signature-tail-check\n",
|
||||
)
|
||||
modified = capsule + tail
|
||||
output_path.write_bytes(modified)
|
||||
return modified
|
||||
|
||||
|
||||
def submit(url: str, capsule: bytes) -> dict:
|
||||
request = urllib.request.Request(
|
||||
url,
|
||||
data=capsule,
|
||||
headers={"Content-Type": "application/octet-stream"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=5) as response:
|
||||
return json.loads(response.read().decode())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--capsule",
|
||||
default="../public/release.capsule",
|
||||
help="path to the original release capsule",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out",
|
||||
default="modified.capsule",
|
||||
help="where to write the modified capsule",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--url",
|
||||
default="http://127.0.0.1:31337/submit",
|
||||
help="SealTail /submit endpoint",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-submit",
|
||||
action="store_true",
|
||||
help="only write the modified capsule",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
capsule_path = pathlib.Path(args.capsule)
|
||||
output_path = pathlib.Path(args.out)
|
||||
modified = build_modified_capsule(capsule_path, output_path)
|
||||
print(f"wrote {output_path} ({len(modified)} bytes)")
|
||||
|
||||
if args.no_submit:
|
||||
return 0
|
||||
|
||||
result = submit(args.url, modified)
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
flag = result.get("flag")
|
||||
if not isinstance(flag, str) or not re.fullmatch(r"caplag\{[^{}]+\}", flag):
|
||||
raise SystemExit("flag was not returned")
|
||||
print(flag)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user