Argument Injection в архиваторе файлов
Обработчик архивации файлов использует exec.Command без shell, но не фильтрует аргументы, начинающиеся с '-'. Атакующий может внедрить произвольные флаги команды tar.
hardgolangPro
Задача
# Argument Injection (RCE через флаги tar)
## Сценарий
Вы проводите аудит **SecureShop**. Разработчик добавил API для архивации пользовательских файлов: `POST /api/archive` принимает массив имён файлов и кладёт их в `tar.gz`. Команда вызывается через `exec.Command("tar", args...)` — **без оболочки**, разделёнными аргументами. Архитектор уверен: «sh не участвует, значит command injection невозможен». Однако безопасное разделение аргументов решает только одну часть проблемы. Если хотя бы один из аргументов начинается с `-`, целевая утилита (`tar`, `git`, `curl`, `find`...) воспринимает его как **собственный флаг** — и многие из этих флагов запускают команды.
## Цель
1. Найдите эндпоинт архивации `/api/archive` и проверьте, что он не валидирует префиксы имён файлов.
2. Подайте в массив `files` строку, начинающуюся с `--`, чтобы передать `tar` собственный опасный флаг.
3. Используйте `tar`-флаг `--checkpoint-action=exec=...` для исполнения произвольной команды.
4. Эскалируйте до admin (например, прочитайте файл с сессионным токеном или флагом, или поднимите свою роль).
5. Получите флаг с `/admin`.
## Теория
**Argument Injection** — класс уязвимостей, при котором атакующий контролирует не код команды и не оболочку, а **отдельные аргументы** программы. Достаточно, чтобы хотя бы один аргумент начинался с `-` или `--` — и утилита воспримет его как собственный флаг. Многие легитимные флаги популярных утилит фактически дают RCE или произвольный read/write:
* `tar --checkpoint=1 --checkpoint-action=exec=CMD` — `tar` исполняет `CMD` при достижении контрольной точки.
* `find . -exec CMD {} \;` — `find` запускает `CMD` для каждого найденного файла.
* `git --upload-pack="cmd"` — `git` исполняет `cmd` при клоне.
* `curl -K /etc/passwd` — `curl` читает свою конфигурацию из произвольного файла.
* `wget --use-askpass=CMD` — `wget` исполняет `CMD` для получения пароля.
* `zip --unzip-command="CMD"` — старый zip позволяет это поле.
Корректная защита делается на двух уровнях:
1. **End-of-options marker `--`** — после него все следующие токены гарантированно интерпретируются как позиционные аргументы, а не флаги. Стандартная конструкция: `tar -czf out.tgz -- $userFiles`.
2. **Валидация префикса** — отклонять любой пользовательский аргумент, начинающийся с `-`, плюс allowlist допустимых символов в имени файла.
**Уязвимый паттерн:**
```go
func (h *Handler) ArchiveFiles(w http.ResponseWriter, r *http.Request) {
var req struct{ Files []string `json:"files"` }
json.NewDecoder(r.Body).Decode(&req)
// Аргументы разделены — sh не участвует. Но префикс "-" не проверен:
args := []string{"-czf", "/tmp/backup.tar.gz"}
for _, f := range req.Files {
args = append(args, f) // ← сюда попадает "--checkpoint-action=exec=..."
}
cmd := exec.Command("tar", args...)
cmd.Run()
}
```
## Точка входа атаки
| Параметр | Значение |
|----------|----------|
| Обычный пользователь | `demo` / `demo` |
| Уязвимый эндпоинт | `POST /api/archive` (JSON `{"files": ["..."]}`) |
| Цель | `GET /admin` (требует роль `admin`, показывает флаг) |