Как выявить экплойт в этой модели чата?

107
01 августа 2021, 02:30

есть модель чата :

<?php
  class Chat extends Model {
    public $table = '#prefix#rooms';
    public $key   = 'id';
    public static $limit = 5;
    /**
     * Последние 10 сообщений в чате;
     *
     * Если указан $room_id, значит запрос со стороны админа
     */
    public static function getMessages($page = 1, $room_id = false) {
      if ( $room_id === false ) {
        $room_id = Client::get('id_chat');
      }
      $offset = ((int)$page - 1) * self::$limit;
      $msgs = DB::fetch('
        select
          id, admin, body, ts, user
        from
          #prefix#messages
        where
          id_room = ? order by ts desc limit ' . self::$limit . ' offset ' . $offset, (int)$room_id);
      if ( $msgs ) {
        // получаем вложения для каждого сообщения
        foreach( $msgs as $i => $item ) {
          $msgs[ $i ]->attachments = DB::shift_array('select attachment from #prefix#attachments where id_message = ? order by attachment asc', $item->id);
        }
      }
      return $msgs;
    }
    /**
     * Общее количество сообщений в комнате;
     *
     * Если указан $id, значит запрос со стороны админа
     */
    public static function totalMessages($room_id = false) {
      if ( $room_id === false ) {
        $room_id = Client::get('id_chat');
      }
      return DB::shift('select count(*) as c from #prefix#messages where id_room = ?', (int)$room_id);
    }
    /**
     * Добавление нового сообщения в чат
     *
     * @param string $mesage - сообщение
     * @param bool   $admin  - ответ администратора или пользователя
     *
     * @return bool | object
     */
    public static function addMessage(&$data, $admin = false, $id_room = false) {
      if ( $id_room === false ) {
        $id_room = (int)Client::get('id_chat');
      }
      $message   = preg_replace('#\n#ui', '<br />', htmlspecialchars($data['body']));
      $ts        = gmdate('Y-m-d H:i:s');
      $id_client = DB::shift('select id from #prefix#clients where id_chat = ? limit 1', $id_room);
      // добавляем сообщение
      $res = DB::execute(
        'insert into #prefix#messages (id_room, admin, body, ts) values(?, ?, ?, ?)',
        (int)$id_room,
        (int)$admin,
        $message,
        $ts
      );
      if ( $admin ) {
        $data['attachments'] = array();
        if ( $_FILES && $_FILES['attachments'] && $_FILES['attachments']['tmp_name'][0] ) {
          // загрузка вложений админом
          $uploaddir = get_document_root() . 'u_files/' . $id_client . '/chat/';
          if ( !is_dir($uploaddir) ) {
            mkdir($uploaddir, 0755, true);
          }
          if ( !is_writable($uploaddir) ) {
            chmod($uploaddir, 0755);
          }
          foreach( $_FILES['attachments']['name'] as $k => $name ) {
            $ext = strtolower(preg_replace('#^.*\.(\w+)$#ui', '$1', $name));
            if ( !in_array(
              $ext,
              [
                'jpg', 'jpeg', 'png',
                'txt', 'csv', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'odt', 'odp', 'ods',
                'zip', 'rar',
                'mp4', 'avi', 'mpg', 'wma', 'flv', 'webm'
              ]
            )
            ) {
              continue;
            }
            $filename = preg_replace('#^(.*)\.\w+$#ui', '$1', $name);
            $filename = URLify::filter($filename) . '.' . $ext;
            move_uploaded_file($_FILES['attachments']['tmp_name'][ $k ], $uploaddir . $filename);
            $data['attachments'] [] = '/u_files/' . $id_client . '/chat/' . $filename;
            // права на сам файл
            chmod(get_document_root() . '/u_files/' . $id_client . '/chat/' . $filename, 0644);
          }
        }
      }
      if ( isset($data['attachments']) && $data['attachments'] ) {
        // если есть вложения, прикрепляем их к сообщению
        $message_id = DB::last_id();
        foreach( $data['attachments'] as $attachment ) {
          $attachment=conf('e_url', 'url').$attachment;
          DB::execute('insert into #prefix#attachments (id_message, attachment) values(?, ?)', $message_id, $attachment);
        }
      }
      // обновляем счетчик непрочитанных сообщений для пользователя
      if ( !$admin ) {
        DB::execute('update #prefix#rooms set unread = ? where id = ? limit 1', ($admin ? 2 : 1), $id_room);
      }
      if ( $res ) {
        if ( !$admin ) {
          user\Notices::admin_newChatMessage(Client::$user);
        } else {
          user\Notices::client_newChatMessage($id_room);
        }
        return (object)array(
          'admin'       => $admin,
          'body'        => $message,
          'attachments' => $data['attachments'] ? $data['attachments'] : false,
          'ts'          => $ts
        );
      }
      return false;
    }
    /**
     * Добавление нового сообщения в чат версия 2 с указаним ид пользователя
     *
     * @param string $mesage - сообщение
     * @param bool   $admin  - ответ администратора или пользователя
     *
     * @return bool | object
     */
     public static function addMessage2(&$data, $admin = false, $id_room = false, $user = 0 ) {
      if ( $id_room === false ) {
        $id_room = (int)Client::get('id_chat');
      }
      $message   = preg_replace('#\n#ui', '<br />', htmlspecialchars($data['body']));
      $ts        = gmdate('Y-m-d H:i:s');
      $id_client = DB::shift('select id from #prefix#clients where id_chat = ? limit 1', $id_room);
      // добавляем сообщение
      $res = DB::execute(
        'insert into #prefix#messages (id_room, admin, body, ts, user) values(?, ?, ?, ?, ?)',
        (int)$id_room,
        (int)$admin,
        $message,
        $ts,
    $user
      );
      if ( $admin ) {
        $data['attachments'] = array();
        if ( $_FILES && $_FILES['attachments'] && $_FILES['attachments']['tmp_name'][0] ) {
          // загрузка вложений админом
          $uploaddir = get_document_root() . 'u_files/' . $id_client . '/chat/';
          if ( !is_dir($uploaddir) ) {
            mkdir($uploaddir, 0755, true);
          }
          if ( !is_writable($uploaddir) ) {
            chmod($uploaddir, 0755);
          }
          foreach( $_FILES['attachments']['name'] as $k => $name ) {
            $ext = strtolower(preg_replace('#^.*\.(\w+)$#ui', '$1', $name));
            if ( !in_array(
              $ext,
              [
                'jpg', 'jpeg', 'png',
                'txt', 'csv', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'odt', 'odp', 'ods',
                'zip', 'rar',
                'mp4', 'avi', 'mpg', 'wma', 'flv', 'webm'
              ]
            )
            ) {
              continue;
            }
            $filename = preg_replace('#^(.*)\.\w+$#ui', '$1', $name);
            $filename = URLify::filter($filename) . '.' . $ext;
            move_uploaded_file($_FILES['attachments']['tmp_name'][ $k ], $uploaddir . $filename);
            $data['attachments'] [] = '/u_files/' . $id_client . '/chat/' . $filename;
            // права на сам файл
            chmod(get_document_root() . '/u_files/' . $id_client . '/chat/' . $filename, 0644);
          }
        }
      }
      if ( isset($data['attachments']) && $data['attachments'] ) {
        // если есть вложения, прикрепляем их к сообщению
        $message_id = DB::last_id();
        foreach( $data['attachments'] as $attachment ) {
          $attachment=conf('easyship_url', 'url').$attachment;
          DB::execute('insert into #prefix#attachments (id_message, attachment) values(?, ?)', $message_id, $attachment);
        }
      }
      // обновляем счетчик непрочитанных сообщений для пользователя
      if ( !$admin ) {
        DB::execute('update #prefix#rooms set unread = ? where id = ? limit 1', ($admin ? 2 : 1), $id_room);
      }
      if ( $res ) {
        if ( !$admin ) {
          user\Notices::admin_newChatMessage(Client::$user);
        } else {
          user\Notices::client_newChatMessage($id_room);
        }
        return (object)array(
          'admin'       => $admin,
          'body'        => $message,
          'attachments' => $data['attachments'] ? $data['attachments'] : false,
          'ts'          => $ts
        );
      }
      return false;
    }
    /**
     * Подсчитываем кол-во непрочитанных пользователем сообщений
     */
    public static function getUnreadMessages() {
      $id_room = (int)Client::get('id_chat');
      $res = DB::single("select unread, last_check from #prefix#chat_rooms where id = ? limit 1", $id_room);
      if ( $res && $res->unread == 2 ) {
        // если стоит отметка, о новых сообщениях для пользователя
        return DB::shift("
          select
            count(*) as c
          from
            #prefix#chat_messages
          where
            id_room = ? and admin = 1
            and id > (select id from #prefix#messages where id_room = ? and admin = 0 order by ts desc limit 1)
            and ts > ?
          order by ts desc", $id_room, $id_room, $res->last_check);
      }
      return false;
    }
    /**
     * Сбрасываем флаг непрочитанных сообщений;
     * frontend
     */
    public static function resetUnread() {
      $id_room = (int)Client::get('id_chat');
      /**
       * Сбрасываем только, если комната была помечена,
       * как непрочитанная для клиента(не для админа)
       */
      DB::execute("update #prefix#chat_rooms set unread = 0, last_check = ? where id = ? and unread = 2 limit 1", gmdate('Y-m-d H:i:s'), $id_room);
    }
    /**
     * Перемещает файлы из временной папки в папку пользователя
     *
     * @param array $attachments - tmp attachments
     *
     * @return array - real attachments
     */
    public static function mvTmpUploads(&$attachments = array()) {
      $root       = getcwd();
      $user_dir   = $root . '/userfiles/' . Client::get('id') . '/';
      $session_id = session_id();
      /**
       * все файлы загружаемые пользователем до отправки сообщения в чате,
       * находятся во временной дирректории с именем его сессии;
       */
      $tmp_dir = "$root/tmp/$session_id/";
      if ( !is_dir($user_dir) ) {
        @mkdir($user_dir, 0755, true);
      } elseif ( !is_writable($user_dir) ) {
        @chmod($user_dir, 0755);
      }
      $res = array();
      if ( $attachments ) {
        foreach( $attachments as $item ) {
          if ( @is_file($tmp_dir . $item) ) {
            $i            = 1;
            $new_filename = $item;
            /**
             * При переносе файла из временной дирректории в папку пользователя,
             * может случиться, что файл с таким именем уже существует,
             * поэтому ставим номер копии в имени файла;
             *
             * @todo наверно while без выхода не очень хорошо
             */
            while( @is_file($user_dir . $new_filename) ) {
              $new_filename = preg_replace("#^(.+)\.(\w+)$#ui", "$1-$i.$2", $item);
              ++$i;
            }
            if ( @rename($tmp_dir . $item, $user_dir . $new_filename) ) {
              $res [] = '/u_files/' . Client::get('id') . '/' . $new_filename;
            }
          }
        }
      }
      // @todo сборщик мусора
      return $res;
    }
  }
?>

Предположительно через эту форму, был скомпроментирован сайт, пользователь смог удалить свою переписку(без прямого доступа к базе, по логам в неё не входили, через админку также не редактировалось, т.к все изменения записываются ).

База mariadb 5.56 php 7.0

Как можно отследить и устранить уязвимость?

READ ALSO
SQL 5.7 group by по максимальному значению

SQL 5.7 group by по максимальному значению

столкнулся с проблемой при переходе MYSQL 5,6 на 5,7 выборка выбирает не корректно

147
Не работают динамические стили Wordpress

Не работают динамические стили Wordpress

Гружу динамические стили через настройки wordpress, код вывода стилей выглядит так

217
.htaccess удалён, но воздействие осталось

.htaccess удалён, но воздействие осталось

Я настроилhtaccess и он прекрасно работал

149
Не работает дебаг в связке PHPStorm + xdebug

Не работает дебаг в связке PHPStorm + xdebug

Xdebug настроенСтоит xdebug helper

84