CTFTimeParser — CTFTime сайтынан алдағы CTF жарыстары туралы хабарландыруларды жинап, MySQL дерекқорына сақтайтын және Telegram супертопқа жариялайтын жеңіл PHP + MySQL құралы.
- CTFTime API арқылы оқиғаларды автоматты жинау
- Екі сатылы буфер: деректер қайталанбауы үшін алдын ала сүзгіден өтеді
- XSS, SSTI, SQLi, SSRF және күдікті URL-дерге қарсы мазмұн қауіпсіздігі тексерулері
- Қауіпті оқиғалар
is_safe=0белгісімен сақталып, жариялаудан ұсталады - Апталық дайджест — дүйсенбі сайын 07:00-де келесі 14 күннің барлық оқиғалары
- Күнделікті жаңартулар — жаңа оқиғаларды толық форматта жариялау
- Сыртқы тәуелділіктер жоқ — таза PHP 8 + PDO + cURL
- Атомдық блокировка файлдары cron-процестерінің қабаттасуын болдырмайды
- Файлдық логтау, 5 МБ-тан асқанда автоматты ротация
- PHP 8.0+
- MySQL 8.0+ (немесе MariaDB 10.5+)
- PHP кеңейтімдері:
pdo_mysql,curl,mbstring
CREATE DATABASE ctftimeparser CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'ctfparser'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON ctftimeparser.* TO 'ctfparser'@'localhost';Схеманы қолдану:
mysql -u ctfparser -p ctftimeparser < schema.sql- Telegram-да @BotFather ашып,
/newbotжіберіңіз - Алынған бот токенін көшіріңіз
- Ботты супертопқа хабар жариялау рұқсатымен әкімші ретінде қосыңыз
- Топ параметрлерінде Topics қосып, хабарландырулар тақырыбын жасаңыз
- Сол тақырыптан кез келген хабарды @JsonDumpBot-қа жіберіңіз —
message_thread_idмәнін табыңыз
cp config.php.sample config.phpconfig.php файлын толтырыңыз:
'db' => [
'user' => 'ctfparser',
'pass' => 'strong_password',
...
],
'telegram' => [
'bot_token' => '123456:ABC-your-token',
'chat_id' => '-1001234567890',
'thread_id' => 42,
...
],# CTFTime-дан оқиғаларды әр 6 сағат сайын жинау
0 */6 * * * /usr/bin/php /path/to/parser.php
# Telegram-ға күн сайын 07:00-де жариялау
0 7 * * * /usr/bin/php /path/to/publisher.php
CTFTimeParser/
├── parser.php # CTFTime → MySQL (cron арқылы іске қосылады)
├── publisher.php # MySQL → Telegram (cron арқылы іске қосылады)
├── config.php # Баптаулар мен кіру деректері (gitignored)
├── config.php.sample # Конфигурация үлгісі
├── schema.sql # Дерекқор схемасы
├── src/
│ ├── CtftimeClient.php # CTFTime API клиенті (cURL)
│ ├── ContentSecurity.php # Мазмұн қауіпсіздігі тексерулері
│ ├── Database.php # PDO орауышы, барлық сұраулар
│ ├── Formatter.php # Telegram HTML хабар форматтаушы
│ └── TelegramBot.php # Telegram Bot API клиенті (cURL)
└── logs/
├── parser.log # Парсер логы (5 МБ кезінде ротация)
└── publisher.log # Паблишер логы (5 МБ кезінде ротация)
Cron (әр 6 сағат)
│
▼
parser.php іске қосылады
│
├─► Блокировка тексерісі (/tmp/ctftimeparser.lock)
│ Басқа процесс жұмыс жасаса → дереу шығу
│
├─► Инициализация: config.php → Database (PDO) + CtftimeClient (cURL)
│
├─► 1-қадам: ID жинау
│ CtftimeClient → CTFTime API (келесі N күн)
│ Алынған ID-лар → parser_buffer (INSERT IGNORE)
│
├─► 2-қадам: Дедупликация
│ ctf_events-те бар ID-ларды parser_buffer-дан жою
│
├─► 3-қадам: Деректерді жүктеп сақтау (әр жаңа ID үшін)
│ │
│ ├─► CtftimeClient → толық оқиға деректері (JSON)
│ │
│ ├─► ID жолдан алынады (response body-дан емес) — спуфинг қорғанысы
│ │
│ ├─► ContentSecurity::sanitize()
│ │ strip_tags + htmlspecialchars (XSS)
│ │ SSTI/SQLi үлгілерін тексеру
│ │ URL схемасы + SSRF тексерісі
│ │ Өріс ұзындығы шектеулері
│ │
│ ├─► Санитизация сәтсіз → буферден жою, өткізіп жіберу
│ │
│ ├─► is_safe=0 → ctf_events-ке сақтау (жарияланбайды)
│ │
│ ├─► is_safe=1 → ctf_events-ке сақтау (жариялауға дайын)
│ │
│ └─► Сұраулар арасында 1 секунд үзіліс
│
├─► Лог жазылады (5 МБ-тан асса — ротация)
│
└─► Блокировка босатылады (бұзылу болса — OS автоматты босатады)
publisher.php (cron, күн сайын 07:00)
│
├─► Блокировка тексерісі (/tmp/ctftimepublisher.lock)
│
├─► Инициализация: config.php → Database + TelegramBot
│
├─► Дүйсенбі → Апталық дайджест
│ Database::getUpcomingEvents(14)
│ Formatter::digest() → 1..N бөлік (≤4096 таңба)
│ TelegramBot::sendMessage() → барлық бөліктер жіберіледі
│ posted_at БЕЛГІЛЕНБЕЙДІ (оқиғалар күнделікті жаңартуда да шығады)
│
└─► Сейсенбі–Жексенбі → Күнделікті жаңартулар
ctf_events WHERE is_safe=1 AND posted_at IS NULL
Formatter::event() → толық Telegram HTML хабары
TelegramBot::sendMessage() → жіберу → posted_at = NOW()
Сәтсіз жіберулер → келесі іске қосуда қайталанады
Дүйсенбілік дайджест форматы:
📋 CTF Events — next 14 days
26 Mar – 09 Apr 2026
• SomeCTF 2026
📅 28 Mar — 30 Mar | Jeopardy | Online
Күнделікті жаңарту форматы:
🚩 SomeCTF 2026
📅 28 Mar — 30 Mar 2026 (UTC)
🏆 Jeopardy | Weight: 25.50
🌐 Online
Оқиға сипаттамасы…
🔗 Event site · CTFTime
logs/ каталогына жазу кезінде Permission denied
chmod 755 logs/PDO connection failed / Access denied for user
config.phpдеректерін тексеріңіз- Пайдаланушы артықшылықтарын растаңыз:
SHOW GRANTS FOR 'ctfparser'@'localhost';
Could not create lock file
/tmpкаталогының PHP процесіне жазу рұқсаты бар екенін тексеріңіз
Парсер іске қосылғаннан кейін оқиғалар пайда болмайды
- CTFTime API келесі 14 күнде оқиға жоқ болса бос жауап қайтарады — бұл қалыпты
logs/parser.logфайлындағы API қателерін тексеріңіз
Publisher ештеңе жібермейді
- Алдымен парсерді іске қосыңыз (
ctf_eventsкестесін толтыру үшін) is_safe = 1жәнеposted_at IS NULLбар жолдардың бар екенін тексеріңізconfig.php-тегі бот токені мен chat/thread ID дұрыстығын тексеріңіз
Another instance is already running
flock()процесс аяқталғанда автоматты босатылады — ағымдағы іске қосу аяқталғанша күтіңіз немесеphp parser.php/php publisher.phpпроцесі жоқ екенін тексеріңіз
CTFTimeParser — лёгкий PHP + MySQL инструмент, который собирает объявления о предстоящих CTF-соревнованиях с CTFTime, сохраняет их в базу данных и публикует в Telegram-супергруппе.
- Автоматический сбор событий через CTFTime API
- Двухэтапный буфер: дедупликация перед загрузкой деталей
- Проверки безопасности контента: XSS, SSTI, SQLi, SSRF, подозрительные URL
- Небезопасные события сохраняются с флагом
is_safe=0и не публикуются - Еженедельный дайджест — каждый понедельник в 07:00, список всех событий на 14 дней
- Ежедневные обновления — каждый день в 07:00, полный пост для каждого нового события
- Без внешних зависимостей — чистый PHP 8 + PDO + cURL
- Атомарные блокировки предотвращают параллельный запуск cron-задач
- Файловое логирование с автоматической ротацией при 5 МБ
- PHP 8.0+
- MySQL 8.0+ (или MariaDB 10.5+)
- PHP-расширения:
pdo_mysql,curl,mbstring
CREATE DATABASE ctftimeparser CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Выделенный пользователь с минимальными привилегиями, не root
CREATE USER 'ctfparser'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON ctftimeparser.* TO 'ctfparser'@'localhost';Применить схему:
mysql -u ctfparser -p ctftimeparser < schema.sql- Откройте @BotFather в Telegram и отправьте
/newbot - Скопируйте полученный токен бота
- Добавьте бота в супергруппу как администратора с правом публикации сообщений
- Включите Topics в настройках группы, создайте тему для анонсов
- Перешлите любое сообщение из этой темы боту @JsonDumpBot — найдите значение
message_thread_id
cp config.php.sample config.phpЗаполните config.php:
'db' => [
'user' => 'ctfparser',
'pass' => 'strong_password',
...
],
'telegram' => [
'bot_token' => '123456:ABC-your-token',
'chat_id' => '-1001234567890',
'thread_id' => 42,
...
],# Собирать события с CTFTime каждые 6 часов
0 */6 * * * /usr/bin/php /path/to/parser.php
# Публиковать в Telegram каждый день в 07:00
0 7 * * * /usr/bin/php /path/to/publisher.php
CTFTimeParser/
├── parser.php # CTFTime → MySQL (запускается через cron)
├── publisher.php # MySQL → Telegram (запускается через cron)
├── config.php # Настройки и учётные данные (gitignored)
├── config.php.sample # Шаблон конфигурации
├── schema.sql # Схема базы данных
├── src/
│ ├── CtftimeClient.php # Клиент CTFTime API (cURL)
│ ├── ContentSecurity.php # Проверки безопасности контента
│ ├── Database.php # Обёртка PDO, все запросы
│ ├── Formatter.php # Форматтер Telegram HTML-сообщений
│ └── TelegramBot.php # Клиент Telegram Bot API (cURL)
└── logs/
├── parser.log # Лог парсера (ротация при 5 МБ)
└── publisher.log # Лог паблишера (ротация при 5 МБ)
Cron (каждые 6 часов)
│
▼
parser.php запускается
│
├─► Проверка блокировки (/tmp/ctftimeparser.lock)
│ Другой процесс работает → немедленный выход
│
├─► Инициализация: config.php → Database (PDO) + CtftimeClient (cURL)
│
├─► Шаг 1: Сбор ID
│ CtftimeClient → CTFTime API (следующие N дней)
│ Полученные ID → parser_buffer (INSERT IGNORE)
│
├─► Шаг 2: Дедупликация
│ Удалить из parser_buffer ID, уже есть в ctf_events
│
├─► Шаг 3: Загрузка и сохранение (для каждого нового ID)
│ │
│ ├─► CtftimeClient → полные данные события (JSON)
│ │
│ ├─► ID берётся из пути запроса (не из тела ответа) — защита от спуфинга
│ │
│ ├─► ContentSecurity::sanitize()
│ │ strip_tags + htmlspecialchars (XSS)
│ │ Проверка паттернов SSTI/SQLi
│ │ Валидация схемы URL + проверка SSRF
│ │ Ограничения длины полей
│ │
│ ├─► Санитизация не прошла → удалить из буфера, пропустить
│ │
│ ├─► is_safe=0 → сохранить в ctf_events (не публикуется)
│ │
│ ├─► is_safe=1 → сохранить в ctf_events (готово к публикации)
│ │
│ └─► Пауза 1 секунда между запросами
│
├─► Запись в лог (ротация при превышении 5 МБ)
│
└─► Блокировка снимается (при краше — OS снимает автоматически)
publisher.php (cron, каждый день в 07:00)
│
├─► Проверка блокировки (/tmp/ctftimepublisher.lock)
│
├─► Инициализация: config.php → Database + TelegramBot
│
├─► Понедельник → Еженедельный дайджест
│ Database::getUpcomingEvents(14)
│ Formatter::digest() → 1..N частей (≤4096 символов каждая)
│ TelegramBot::sendMessage() → отправка всех частей
│ posted_at НЕ устанавливается (события появятся в ежедневных обновлениях)
│
└─► Вт–Вс → Ежедневные обновления
ctf_events WHERE is_safe=1 AND posted_at IS NULL
Formatter::event() → полное HTML-сообщение для Telegram
TelegramBot::sendMessage() → отправка → posted_at = NOW()
Неудачные отправки → остаются неопубликованными, повторяются при следующем запуске
Формат дайджеста (понедельник):
📋 CTF Events — next 14 days
26 Mar – 09 Apr 2026
• SomeCTF 2026
📅 28 Mar — 30 Mar | Jeopardy | Online
Формат ежедневного обновления:
🚩 SomeCTF 2026
📅 28 Mar — 30 Mar 2026 (UTC)
🏆 Jeopardy | Weight: 25.50
🌐 Online
Краткое описание события…
🔗 Event site · CTFTime
| Столбец | Тип | Описание |
|---|---|---|
event_id |
INT UNSIGNED PK | ID события CTFTime |
created_at |
DATETIME | Время создания записи |
| Столбец | Тип | Описание |
|---|---|---|
id |
INT UNSIGNED PK | ID события CTFTime |
title |
VARCHAR(255) | Название события |
url |
VARCHAR(512) | Официальный сайт события |
ctftime_url |
VARCHAR(512) | Страница события на CTFTime |
start_time |
DATETIME | Начало (UTC) |
finish_time |
DATETIME | Конец (UTC) |
format |
VARCHAR(64) | Jeopardy / Attack-Defense / и др. |
weight |
DECIMAL(8,5) | Рейтинговый вес CTFTime |
onsite |
TINYINT(1) | 1 = очное мероприятие |
location |
VARCHAR(255) | Город/страна для очных событий |
description |
TEXT | Описание события |
logo_url |
VARCHAR(512) | URL логотипа |
is_safe |
TINYINT(1) | 0 = помечено проверкой безопасности |
posted_at |
DATETIME | NULL = ещё не опубликовано в Telegram |
created_at |
DATETIME | Время создания записи |
Аудит по OWASP Top 10:2025.
| OWASP | Угроза | Защита |
|---|---|---|
| A01 | Broken Access Control / SSRF | CURLOPT_FOLLOWLOCATION=false во всех cURL-запросах; только HTTPS; URL API захардкожен; учётные данные в URL отклоняются; проверка приватных IP-диапазонов |
| A02 | Security Misconfiguration | Выделенный пользователь БД с минимальными привилегиями; config.php исключён из VCS; токен бота не пишется в логи |
| A05 | Injection — SQLi | PDO prepared statements повсюду; никакой интерполяции пользовательских данных |
| A05 | Injection — SSTI | Regex-детекция паттернов {{ }}, {% %}, <% %>, ${}, #{} |
| A05 | Injection — XSS | strip_tags() + htmlspecialchars() на всех строковых полях до сохранения и вывода |
| A06 | Insecure Design | Нет блокирующего DNS; белый список схем URL (http/https); ограничения длины полей |
| A08 | Data Integrity | ID события берётся из пути запроса, а не из тела ответа; лимит глубины JSON |
| A09 | Security Logging Failures | Структурированный лог с уровнем + временной меткой; ротация при 5 МБ; отдельные логи для каждого компонента |
| A10 | Mishandling of Exceptional Conditions | Атомарная блокировка fopen('c')+flock(LOCK_EX|LOCK_NB) — нет гонки TOCTOU; OS снимает блокировку при краше; обработка Telegram 429 с retry_after |
Каждый компонент пишет в свой лог-файл, ротация при 5 МБ:
logs/parser.log
[2026-03-26 12:00:00] [INFO] Fetching event list [2026-03-26 – 2026-04-09]
[2026-03-26 12:00:01] [INFO] Received 14 event IDs from API.
[2026-03-26 12:00:01] [INFO] Buffer cleaned (removed already-known events).
[2026-03-26 12:00:01] [INFO] 3 new event(s) to process.
[2026-03-26 12:00:02] [INFO] Event #2345 saved: "SomeCTF 2026" (safe=1)
[2026-03-26 12:00:04] [INFO] Done. Saved: 3 | Unsafe (stored): 0 | Skipped: 0
logs/publisher.log
[2026-03-30 07:00:00] [INFO] Daily update: 3 event(s) to publish.
[2026-03-30 07:00:01] [INFO] Published event #2345: "SomeCTF 2026"
[2026-03-30 07:00:03] [INFO] Published event #2346: "AnotherCTF 2026"
[2026-03-30 07:00:05] [INFO] Daily update done. Sent: 3 | Failed: 0
Permission denied при записи в logs/
chmod 755 logs/PDO connection failed / Access denied for user
- Проверьте учётные данные в
config.php - Убедитесь в правах пользователя:
SHOW GRANTS FOR 'ctfparser'@'localhost';
Could not create lock file
- Проверьте права на запись в
/tmpдля PHP-процесса
После запуска парсера события не появляются
- CTFTime API возвращает пустой список, если нет событий на 14 дней — это нормально
- Проверьте
logs/parser.logна наличие ошибок API
Publisher ничего не отправляет
- Сначала запустите парсер, чтобы заполнить
ctf_events - Убедитесь, что есть строки с
is_safe = 1иposted_at IS NULL - Проверьте токен бота и chat/thread IDs в
config.php
Another instance is already running
flock()снимается автоматически при завершении процесса — дождитесь окончания текущего запуска или убедитесь, чтоphp parser.php/php publisher.phpне запущен
CTFTimeParser is a lightweight PHP + MySQL parser that fetches upcoming CTF event announcements from CTFTime and stores them for publication in a Telegram supergroup topic.
- Fetches events via the CTFTime public API
- Two-stage buffer pipeline — deduplication before fetching details
- Content security checks: XSS, SSTI, SQLi, SSRF, suspicious URLs
- Unsafe events are stored with
is_safe=0and held back from publication - Weekly digest every Monday at 07:00 — compact list of all events for the next 14 days
- Daily updates every day at 07:00 — individual full-detail posts for new events
- No external dependencies — pure PHP 8 + PDO + cURL
- Atomic lock files prevent overlapping cron runs
- File-based logging with automatic rotation at 5 MB
- PHP 8.0+
- MySQL 8.0+ (or MariaDB 10.5+)
- PHP extensions:
pdo_mysql,curl,mbstring
CREATE DATABASE ctftimeparser CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Use a dedicated user with minimum required privileges, not root
CREATE USER 'ctfparser'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON ctftimeparser.* TO 'ctfparser'@'localhost';Apply the schema:
mysql -u ctfparser -p ctftimeparser < schema.sql- Open @BotFather in Telegram and send
/newbot - Copy the bot token you receive
- Add the bot to your supergroup as an administrator with Post Messages permission
- Enable Topics in the group settings, then create the announcements topic
- Forward any message from that topic to @JsonDumpBot — find
message_thread_idin the output
cp config.php.sample config.phpFill in config.php with your database credentials and Telegram settings:
'db' => [
'user' => 'ctfparser',
'pass' => 'strong_password',
...
],
'telegram' => [
'bot_token' => '123456:ABC-your-token',
'chat_id' => '-1001234567890',
'thread_id' => 42,
...
],# Fetch new events from CTFTime every 6 hours
0 */6 * * * /usr/bin/php /path/to/parser.php
# Publish to Telegram every day at 07:00
0 7 * * * /usr/bin/php /path/to/publisher.php
CTFTimeParser/
├── parser.php # CTFTime → MySQL pipeline (run via cron)
├── publisher.php # MySQL → Telegram publisher (run via cron)
├── config.php # Credentials and settings (gitignored)
├── config.php.sample # Configuration template
├── schema.sql # Database schema
├── src/
│ ├── CtftimeClient.php # CTFTime API client (cURL)
│ ├── ContentSecurity.php # Input sanitization and threat detection
│ ├── Database.php # PDO wrapper, all queries
│ ├── Formatter.php # Telegram HTML message formatter
│ └── TelegramBot.php # Telegram Bot API client (cURL)
└── logs/
├── parser.log # Parser log (auto-created, rotates at 5 MB)
└── publisher.log # Publisher log (auto-created, rotates at 5 MB)
Cron (every 6 hours)
│
▼
parser.php runs
│
├─► Lock check (/tmp/ctftimeparser.lock)
│ Another instance running → exit immediately
│
├─► Bootstrap: config.php → Database (PDO) + CtftimeClient (cURL)
│
├─► Step 1: Collect IDs
│ CtftimeClient → CTFTime API (next N days)
│ Received IDs → parser_buffer (INSERT IGNORE)
│
├─► Step 2: Deduplicate
│ Remove from parser_buffer any IDs already in ctf_events
│
├─► Step 3: Fetch and store (for each new ID)
│ │
│ ├─► CtftimeClient → full event details (JSON)
│ │
│ ├─► Event ID taken from request path (not response body) — anti-spoofing
│ │
│ ├─► ContentSecurity::sanitize()
│ │ strip_tags + htmlspecialchars (XSS)
│ │ SSTI / SQLi pattern detection
│ │ URL scheme validation + SSRF checks
│ │ Field length limits
│ │
│ ├─► Sanitization failed → delete from buffer, skip
│ │
│ ├─► is_safe=0 → store in ctf_events (withheld from publication)
│ │
│ ├─► is_safe=1 → store in ctf_events (ready to publish)
│ │
│ └─► 1-second pause between requests
│
├─► Write to log (rotate when file exceeds 5 MB)
│
└─► Lock released (OS releases automatically on crash)
publisher.php (cron daily at 07:00)
│
├─► Lock check (/tmp/ctftimepublisher.lock)
│
├─► Bootstrap: config.php → Database + TelegramBot
│
├─► Monday → Weekly digest
│ Database::getUpcomingEvents(14)
│ Formatter::digest() → 1..N message parts (≤4096 chars each)
│ TelegramBot::sendMessage() → send all parts
│ posted_at is NOT set (events still appear in daily updates)
│
└─► Tue–Sun → Daily updates
ctf_events WHERE is_safe=1 AND posted_at IS NULL
Formatter::event() → full Telegram HTML message
TelegramBot::sendMessage() → send → posted_at = NOW()
Failed sends → left unpublished, retried next run
Monday digest format:
📋 CTF Events — next 14 days
26 Mar – 09 Apr 2026
• SomeCTF 2026
📅 28 Mar — 30 Mar | Jeopardy | Online
Daily update format:
🚩 SomeCTF 2026
📅 28 Mar — 30 Mar 2026 (UTC)
🏆 Jeopardy | Weight: 25.50
🌐 Online
Brief description…
🔗 Event site · CTFTime
| Column | Type | Description |
|---|---|---|
event_id |
INT UNSIGNED PK | CTFTime event ID |
created_at |
DATETIME | Row creation time |
| Column | Type | Description |
|---|---|---|
id |
INT UNSIGNED PK | CTFTime event ID |
title |
VARCHAR(255) | Event name |
url |
VARCHAR(512) | Official event website |
ctftime_url |
VARCHAR(512) | CTFTime event page |
start_time |
DATETIME | Start (UTC) |
finish_time |
DATETIME | End (UTC) |
format |
VARCHAR(64) | Jeopardy / Attack-Defense / etc. |
weight |
DECIMAL(8,5) | CTFTime rating weight |
onsite |
TINYINT(1) | 1 = on-site event |
location |
VARCHAR(255) | City/country for on-site events |
description |
TEXT | Event description |
logo_url |
VARCHAR(512) | Event logo URL |
is_safe |
TINYINT(1) | 0 = flagged by security checks |
posted_at |
DATETIME | NULL = not yet published to Telegram |
created_at |
DATETIME | Row creation time |
Events with is_safe = 0 are stored but not published until manually reviewed.
Audited against OWASP Top 10:2025.
| OWASP | Threat | Defence |
|---|---|---|
| A01 | Broken Access Control / SSRF | CURLOPT_FOLLOWLOCATION=false in all cURL calls; HTTPS-only; API base URL hardcoded; credentials in URL rejected; private IP range check on literal IPs |
| A02 | Security Misconfiguration | Dedicated DB user with minimum privileges; config.php excluded from VCS; bot token never written to logs |
| A05 | Injection — SQLi | PDO prepared statements throughout; no user-data interpolation; supplementary regex detection in stored content |
| A05 | Injection — SSTI | Regex detection of {{ }}, {% %}, <% %>, ${}, #{} patterns |
| A05 | Injection — XSS | strip_tags() + htmlspecialchars() on all string fields before storage and before output |
| A06 | Insecure Design | No blocking DNS resolution; URL scheme whitelist (http/https); field length limits |
| A08 | Data Integrity | Event ID overridden from request path, not response body; JSON depth limited |
| A09 | Security Logging Failures | Structured log with level + timestamp; automatic 5 MB rotation; separate logs per component |
| A10 | Mishandling of Exceptional Conditions | Atomic lock via fopen('c')+flock(LOCK_EX|LOCK_NB) — no TOCTOU race, OS auto-releases on crash; Telegram 429 handled with retry_after back-off |
Events that fail any content check are stored with is_safe=0 and withheld from publication pending manual review.
Each component writes to its own log file, rotated automatically at 5 MB:
logs/parser.log
[2026-03-26 12:00:00] [INFO] Fetching event list [2026-03-26 – 2026-04-02]
[2026-03-26 12:00:01] [INFO] Received 14 event IDs from API.
[2026-03-26 12:00:01] [INFO] Buffer cleaned (removed already-known events).
[2026-03-26 12:00:01] [INFO] 3 new event(s) to process.
[2026-03-26 12:00:02] [INFO] Event #2345 saved: "SomeCTF 2026" (safe=1)
[2026-03-26 12:00:04] [INFO] Done. Saved: 3 | Unsafe (stored): 0 | Skipped: 0
logs/publisher.log
[2026-03-30 07:00:00] [INFO] Daily update: 3 event(s) to publish.
[2026-03-30 07:00:01] [INFO] Published event #2345: "SomeCTF 2026"
[2026-03-30 07:00:03] [INFO] Published event #2346: "AnotherCTF 2026"
[2026-03-30 07:00:05] [INFO] Daily update done. Sent: 3 | Failed: 0
Permission denied writing to logs/
chmod 755 logs/PDO connection failed / Access denied for user
- Verify
config.phpcredentials match your MySQL user - Confirm grants:
SHOW GRANTS FOR 'ctfparser'@'localhost';
Could not create lock file
- Check that
/tmpis writable by the PHP process user
No events appear after running the parser
- CTFTime API returns an empty list if no events are scheduled in the next 14 days — this is normal
- Check
logs/parser.logfor API errors
Publisher sends nothing
- Run the parser first so
ctf_eventsis populated - Check that
is_safe = 1andposted_at IS NULLfor at least one row - Verify the bot token and chat/thread IDs in
config.php
Another instance is already running
flock()releases automatically when the process exits — wait for the current run to finish or verify nophp parser.php/php publisher.phpprocess is running