Автогенерируемые аудиокапчи (баг в NetCat 5.2)
Сегодняшний рабочий день мы провели очень интересно - полдня поднимали сервер. Подняли, конечно, и вот решили кратко отчитаться о том, что произошло. История очень показательна, и является блестяще иллюстрированным ответом на вопрос "а зачем нашему сайту платная техподдержка?".
Эта история наверняка касается версии NetCat 5.2 всех редакций, а также модуля "Защита картинкой" и техподдержки NetCat :).
Также она может касаться соседних версий, но точно не 5.5, где этот баг исправлен.
Что случилось?
Итак, все началось где-то на той неделе и проявилось в субботу, когда на нашем сервере неожиданно закончилось место и он начал подтупливать и подглючивать. Сегодня с утра я взялся за всестороннюю диагностику и выяснил, примерно следующую последовательность фактов:
- начиная с субботы, некий ботнет, состоящий преимущественно из взломанных сайтов низшего эшелона (порносайты, знакомства, "женские истории" и т.д.) начал неторопливо "прощупывать" один из наших сайтов на NetCat
- предполагаю, что он искал SQL Injection Netcat 5.0, так как ломился на капчу
- данная уязвимость на сайте закрыта, поэтому ботнет к успеху не пришел. Однако NetCat настолько... ммм... своеобразен, что начал реагировать на "прощупывания", инициализируя процесс генерации аудиокатч.
- следует заметить, что опция (аудиокапчи) в админке вообще-то отключена, но об этом ниже
- в результате аудиокапчи (то есть маленькие mp3-файлики со случайными комбинациями букв) постепенно генерились и генерились до тех пор, пока их не нагенерилось на 5 ГБ.
- так как у нашего сайта на сервере не стояло квоты на использование дискового пространства, место на диске в итоге закончилось полностью
- сервер завис и все сломалось.
Как я все пофиксил?
- сначала я продиагностировал сервер и выяснил, что проблема в огромном множестве mp3-файлов в директории
netcat_files/captcha/current_voice
. - я зашел в админку и обнаружил что данный функционал модуля, вообще-то, выключен! В поле "использовать аудиокачу" стоит ноль!
- я удалил все эти файлы, однако они немедленно начали появляться вновь, таким образом проблема не была решена;
- затем я позвонил в NetCat и попробовал узнать, в курсе ли они такой проблемы. Дело в том, что я вроде уже сталкивался с чем-то похожим когда-то.
- по телефону я не получил никакого ответа, кроме предложения писать тикет. Но это я предполагаю чем закончится: у вас кончилась лицензия, продлите лицензию, о, у вас старая версия, поставьте обновление, о, проблема решена и появилось две новые? пожалуйста, создайте два новых тикета. Я, конечно, утрирую, но ясно, что быстрого решения мне не добиться. Поэтому ковыряюсь сам дальше.
- после анализа error.log выяснилось, что проблема как-то проявляется вот здесь:
[Mon Apr 06 14:55:00 2015] [error] [client 173.252.101.112] PHP Warning: copy(/var/www/*/data/www/*/netcat_files/captcha/current_voice/33843462e319929356b2806dfc07e579.mp3) [<a href='function.copy'>function.copy</a>]: failed to open stream: \xd0\x9e\xd1\x82\xd0\xba\xd0\xb0\xd0\xb7\xd0\xb0\xd0\xbd\xd0\xbe \xd0\xb2 \xd0\xb4\xd0\xbe\xd1\x81\xd1\x82\xd1\x83\xd0\xbf\xd0\xb5 in /var/www/*/data/www/*/netcat/modules/captcha/function.inc.php on line 73
- я посмотрел на код в районе 73-й строчки:
- // Обновление захешированных файлов для аудиокаптчи
- $res = $nc_core->db->get_results("SELECT * FROM `Captchas_Settings`", ARRAY_A);
- if (!empty($res))
- foreach ($res as $v)
- $captcha_settings[$v['Key']] = $v['Value'];
- if ($captcha_settings && is_writable($nc_core->FILES_FOLDER) && is_dir($nc_core->MODULE_FOLDER.'captcha/voice/'.$MODULE_VARS['captcha']['VOICE'].'/') && time() - 3600 >= strtotime($captcha_settings['time'])) {
- $db->query("UPDATE `Captchas_Settings` SET `Value`= Now() WHERE `Key` = 'time'");
- $from = $nc_core->MODULE_FOLDER.'captcha/voice/'.$MODULE_VARS['captcha']['VOICE'].'/';
- $to = $nc_core->FILES_FOLDER.'captcha/current_voice/';
- $nc_core->files->create_dir($to);
- $enc_mp3_files[] = '';
- $enc_mp3_folder = opendir($to);
- while ($one = readdir($enc_mp3_folder)) {
- if ($one != '.' && $one != '..' && substr(strrchr($one, '.'), 1) == 'mp3') {
- $enc_mp3_files[] = $one;
- }
- }
- $normal_mp3_folder = opendir($from);
- while ($one = readdir($normal_mp3_folder)) {
- $file_hash = nc_captcha_generate_hash();
- if ($one != '.' && $one != '..' && substr(strrchr($one, '.'), 1) == 'mp3') {
- if ($captcha_settings['current_voice'] != $MODULE_VARS['captcha']['VOICE'] || !in_array($captcha_settings[$one], $enc_mp3_files)) {
- $db->query("UPDATE `Captchas_Settings` SET `Value`= '".$MODULE_VARS['captcha']['VOICE']."' WHERE `Key` = 'current_voice'");
- if ($captcha_settings['current_voice'] != $MODULE_VARS['captcha']['VOICE']) {
- unlink($to.$captcha_settings[$one]);
- }
- copy($from.$one, $to.$file_hash.'.mp3');
- $db->query("UPDATE `Captchas_Settings` SET `Value`='".$db->escape($file_hash).".mp3' WHERE `Key` ='".$one."'");
- } else {
- rename($to.$captcha_settings[$one], $to.$file_hash.'.mp3');
- $db->query("UPDATE `Captchas_Settings` SET `Value`='".$db->escape($file_hash).".mp3' WHERE `Key` ='".$one."'");
- }
- }
- }
- }
- я внимательно посмотрел на этот код. Еще внимательнее... Еще внимательнее... И понял, что в нем нет проверки включенности аудиокапчи.
- взял дистрибутив версии 5.5 и посмотрел аналогичное место. Эврика! В нем код слегка переписан, и как раз появилась недостающая проверка настроек модуля, значит здесь действительно ошибка, и ее уже исправили.
Так как мне не хотелось обновляться (ибо это чревато новыми багами и проблемами) и уж тем более править код самому, в итоге я еще раз проанализировал код и... просто назначил полю VOICE несуществующее значение. Получилось вот так:

Собственно, вот оно и решение данной проблемы, рекомендуемое к превентивному применению всем владельцами версии 5.2 и других соседних, содержащих такой же код.