Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
SP_Intro0(1).pdf
Скачиваний:
17
Добавлен:
11.02.2016
Размер:
487.54 Кб
Скачать
struct spwd
{
char *sp_namp; char *sp_pwdp; long int sp_lstchg; long int sp_min; long int sp_max; long int sp_warn;
long int sp_inact; long int sp_expire;

работать. Что нам остается ?! Ну, я думаю первым делом вы все вызовите команду chmod 644 /etc/passwd. А, вовторых, перепишите код таким образом, чтобы если что-то не так с функцией getpwuid, то программа пользовалась переменными среды, как единственно доступной в данном случае информацией.

Собственно говоря, на этом все. Вы уже начинаете чувствовать, что не все просто в нашем мире linux ?

Шаг 14 - Получение данных из shadow password

В прошлый раз мы с Вами разобрались с тем, как получать информацию о пользователе из файла /etc/passwd, но как оказалось получить пароль нам не удастся, потому что его там нет. А как быть, если он Вам нужен ? Ну, например, Вы решили написать собственный сервер POP3, который для авторизации требует наличие пароля.

Для работы со скрытыми паролями надо подключить файл shadow.h и вам станут доступны аналогичные процедуры:

#include <shadow.h>

struct spwd *getspnam (const char *name);

Обратите внимание, что в файле shadow.h нет определения функции получения информации о пользователе по его UID. Т.е. для работы нужно знать имя пользователя, соответственно сначала нужно воспользоваться функцией getpwuid(), чтобы по UID получить имя.

Функция getspnam() возвращает структуру struct spwd, либо NULL в случае неудачи. Данная структура определена следующим образом:

/* Login name. */

/* Encrypted password. */ /* Date of last change. */

/* Minimum number of days between changes. */ /* Maximum number of days between changes. */ /* Number of days to warn user to change

the password. */

/* Number of days the account may be inactive. */

/* Number of days since 1970-01-01 until account expires. */

unsigned long int sp_flag; /* Reserved. */ };

Как видите структура гораздо больше, чем passwd. Большинство полей отвечает за временные параметры пароля, такие как его минимальное и максимальное время жизни, а также время жизни всего аккаунта.

И наверняка Вы заметили, что я немного соврал Вам в прошлый раз, когда сказал, что /etc/shadow содержит информацию аналогичную /etc/passwd. Получается, что кроме логина и пароля Вы тут не найдете домашней директории и шелла. В принципе верно, зачем хранить одно и тоже в нескольких местах ?! Выходит, что нам не удастся "отбиться от коллектива" и придется пользоваться обоими файлами.

Давайте посмотрим, как работает эта функция. Напишем маленькую программку shadowtest.c:

#include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <pwd.h> #include <shadow.h>

int main(){

struct passwd *userinfo; struct spwd *passw; uid_t userid;

userid = getuid();

userinfo = getpwuid(userid);

if (userinfo != NULL){

passw = getspnam(userinfo->pw_name);

if (passw != NULL){

printf("user login: %s\n",userinfo->pw_name); printf("user home: %s\n",userinfo->pw_dir); printf("user shell: %s\n",userinfo->pw_shell); printf("user password: %s\n",userinfo->pw_passwd);

printf("user shadow password: %s\n",passw->sp_pwdp); printf("user last change: %ld\n",passw->sp_lstchg);

};

};

return 0; };

Компилируем и запускаем:

dron~# ./gcc shadowtest.c -o shadowtest dron~# ./shadowtest

user login: root user home: /root user shell: /bin/bash user password: x

user shadow password: $1$02p9xyDo$gnkh4vts/rArhJselceTV1 user last change: 12028

Как видите пароль нам получить удалось только из структуры struct spwd. Но он зашифрованный алгоритмом MD5, в данном случае настоящий пароль 12345678 (можете не мучаться над взломом :). Тут кстати следует поговорить о том, как хранятся пароли. Понятное дело, что если пароли будут храниться в виде plain text, т.е. в виде текста "как есть", то можно будет узнать пароль для любого пользователя совершенно спокойно. Современные правила безопасности вообще не разрешают хранить пароль в таком виде. Вместо этого пароль хранится в виде хеша от настояшего пароля. Функция вырабатывающая хеш берет настоящий пароль и вырабатывает на его основе уникальную последовательность чисел, которую не возможно обратно преобразовать в пароль, потому что математические функции работающие над выработкой пароля специально создаются однонаправленными. Создание таких процедур является сложной криптографической задачей и порой под силу только крупным научно-исследовательским институтам. К примеру у нас в России существуют засекреченные алгоритмы, некоторые из которых разрабатывались в течение 10 лет, так вот представьте каких трудов это стоит и представьте какие эти алгоритмы совершенные. Взять к примеру наш алгоритм шифрования ГОСТ 28147-89, который существует с 89 года и до сих пор остается одним из самых защищенных (он может иметь длину ключа 256 бит, в то время как DES имеет всего 56 бит и при нынешнем развитии компьютеров является чрезвычайно устаревшим). Однако для выработки хеша в системах Linux используются в основном алгоритмы DES и MD5, хотя первый уже используется крайне редко.

Так вот, сравнение правильности пароля происходит следующим образом. Программа получает настоящий пароль от пользователя, потом вырабатывает на его основе хеш и сравнивает его с тем, который она получила из файла /etc/shadow. Если хеши не совпадают, то значит пароли разные.

Шаг 15 - Работа с паролями системы с помощью функции crypt()

Уже многие из Вас задали себе закономерный вопрос "как же работать с паролями", т.е. как получить тот хеш, о котором шла речь раньше. Давайте раберемся с этим.

За генерацию паролей отвечает функция crypt(). Подключить ее к программе можно так:

#define _XOPEN_SOURCE #include <unistd.h>

char *crypt(const char *key, const char *salt);

Вместо заголовочного файла unistd.h для подключения функций шифрования можно использовать другой файл - crypt.h. В принципе без разницы, они оба имеют одинаковые определения функции crypt().

Для работы функции требуется два параметра:

key - это секретный пароль пользователя, который требуется зашифровать

salt - это так называемый "открытый ключ", или же в понятиях алгоритмов шифрования инициализационные данные. Данный параметр должен состоять из символов a-zA-Z0-9./, т.е. из любых латинских букв, цифр или некоторых символов.

Если же с паролем все ясно, то salt может заставить задуматься. Давайте посмотрим на что влияет этот параметр. Напишем тестовую программку crypt.c:

#include <stdlib.h> #include <crypt.h>

int main(){

printf("crypt(\"password\",\"ab\") = \"%s\"\n",crypt("password","ab")); printf("crypt(\"password\",\"ab12\") = \"%s\"\n",crypt("password","ab12")); printf("crypt(\"password\",\"ac\") = \"%s\"\n",crypt("password","ac")); printf("crypt(\"password\",\"ac123\") = \"%s\"\n",crypt("password","ac123")); printf("crypt(\"password\",\"1a\") = \"%s\"\n",crypt("password","1a")); printf("crypt(\"password\",\"1a.\") = \"%s\"\n",crypt("password","1a.")); return 0;

};

Данная программа пытается выработать хеш с разным сальтом для пароля "password". Компилируем программу: dron~# gcc crypt.c -o crypt

/tmp/ccBXde1R.o: In function `main': /tmp/ccBXde1R.o(.text+0x17): undefined reference to `crypt' /tmp/ccBXde1R.o(.text+0x3f): undefined reference to `crypt' /tmp/ccBXde1R.o(.text+0x67): undefined reference to `crypt' /tmp/ccBXde1R.o(.text+0x8f): undefined reference to `crypt' /tmp/ccBXde1R.o(.text+0xb7): undefined reference to `crypt'

/tmp/ccBXde1R.o(.text+0xdf): more undefined references to `crypt' follow collect2: ld returned 1 exit status

Не так быстро. Просто так откомпилировать программу нельзя, надо обязательно подключить библиотеку libcrypt.a, делается это такой командой:

dron~# gcc crypt.c -o crypt -lcrypt dron~# ./crypt

crypt("password","ab") = "abJnggxhB/yWI" crypt("password","ab12") = "abJnggxhB/yWI" crypt("password","ac") = "acBxUIBkuWIZE" crypt("password","ac123") = "acBxUIBkuWIZE" crypt("password","1a") = "1abtv8E0hkEd6" crypt("password","1a.") = "1abtv8E0hkEd6"

Посмотрите на результат работы функции. Хоть я и задавал разные salt некоторые хеши получились совершенно одинаковыми. Из этого можно сделать вывод и том, что в нем играют роль только первые два символа. Значения этих символов используются для шифрования пароля алгоритмом DES. Заметьте также, что salt входит в хеш как его начальная часть. И это правильно, ведь если его удалить, то неизвестно, что надо использовать для проверки достоверности пароля. На самом деле, как мы узнаем дальше, это сделано специально для того, чтобы можно было без особых мучений с паролем из /etc/shadow сразу же отправлять его в функцию crypt().

Хорошо. Нам теперь, думаю, стало понятно как работает алгоритм DES, а как же MD5 ? Ведь, как я уже говорил, пароли сегодня чаще всего хранятся в виде MD5. Хороший вопрос. Ответ на него я искал некоторое время. Исходя из того, что совершенно точно функция crypt() должна работать с MD5 я начал рыскать в исходнике gnu-pop3d, который совсем недавно чуть ли не переписал заново :) Пролистав все и не обнаружив ни #define ни хитрых #include я попробовал залезть в файл библиотеки /usr/lib/libcrypt.a. Получить список функций из нее можно командой nm:

dron~# nm /usr/lib/libcrypt.a

crypt-entry.o: 0000000000000117 t Letext 0000000000000000 T __crypt_r

U__md5_crypt

U__md5_crypt_r

U_ufc_dofinalperm_r

U_ufc_doit_r

U_ufc_foobar

U_ufc_mk_keytab_r

U_ufc_output_conversion_r

U_ufc_setup_salt_r 00000000000000d0 T crypt 0000000000000000 W crypt_r 00000000000000d0 W fcrypt 0000000000000000 r md5_salt_prefix

Ustrncmp

Ustrncpy

md5-crypt.o: 000000000000070a t Letext

U__assert_fail

U__errno_location 0000000000000690 T __md5_crypt 0000000000000000 T __md5_crypt_r

U__md5_finish_ctx

U__md5_init_ctx

...............

Посмотрите, список функций очень большой, поэтому не привожу весь, но видно определенно, что libcrypt.a содержит функции для работы с MD5. Но тогда как ?! Ведь нет никаких параметров дополнительных. А все оказалось куда проще %) Посмотрите на листинг, видите имя md5_salt_prefix. Не правда ли очень говорящее название ?! А теперь посмотрите типичный пароль закодированный с помощью MD5:

$1$/DrNy/Cv$ZBydbOBsEvdI5u5sib2X/0 $1$02p9xyDo$gnkh4vts/rArhJselceTV1

Не видите ничего странного ?! Правильно, у них структура отличается от паролей на DES и выглядит следующим образом:

$1$..salt..$.........

hash.........

Именно по этой структуре функция crypt() определяет каким методом ей шифровать пароль. Не поленимся однако и посмотрим исходники crypt() в библиотеке libc. Вот к примеру строки из файла crypt-entry.c:

/* Define our magic string to mark salt for MD5 encryption replacement. This is meant to be the same as for other MD5 based encryption implementations. */

static const char md5_salt_prefix[] = "$1$";

.......

/* Try to find out whether we have to use MD5 encryption replacement.*/ if (strncmp (md5_salt_prefix, salt, sizeof (md5_salt_prefix) - 1) == 0) return __md5_crypt_r (key, salt, (char *) data, sizeof (struct crypt_data));

Помоему классно :) Именно "магическая строчка" $1$ и является тем методом переключения между различными алгоритмами. Тут еще интересен вопрос о том, какой длины должен быть этот salt, изучая исходниках дальше Вы сможете найти в файле md5-crypt.c строчки:

/* Find beginning of salt string. The prefix should normally always be present. Just in case it is not. */

if (strncmp (md5_salt_prefix, salt, sizeof (md5_salt_prefix) - 1) == 0) /* Skip salt prefix. */

salt += sizeof (md5_salt_prefix) - 1;

salt_len = MIN (strcspn (salt, "$"), 8); key_len = strlen (key);

Тут не вооруженным глазом видно, что после $1$ ищется второй символ $ и берется длина строки ограниченная этими признаками. Далее выбирается минимум между длиной строки и 8, т.е. получается что salt в алгоритме MD5 может быть любой длины не больше 8-ми. Это и требовалось доказать, теперь давайте попробуем :)

#include <stdlib.h> #include <crypt.h>

int main(){

printf("crypt(\"12345678\",\"$1$abasdlkasl123$\") = \"%s\"\n", crypt("password","$1$abasdlkasl123$"));

printf("crypt(\"12345678\",\"$1$dfg$\") = \"%s\"\n", crypt("password","$1$dfg$"));

return 0;

};

Снова компилируем, и не забываем про библиотеку crypt: dron~# gcc crypt1.c -o crypt1 -lcrypt

dron~# ./crypt1

crypt("12345678","$1$abasdlkasl123$") = "$1$abasdlka$z9aVWR2l14E3WngLCABSt1" crypt("12345678","$1$dfg$") = "$1$dfg$fF0Vo9cC5CyBY827ltEdn0"

Все получилось :) А Вы как думали ?! И обратите внимание на то, что длинный salt в первом случае обрезался до 8-ми символов. Кстати, помоему длина 8 символов куда лучше, чем два. Это еще раз говорит о том, что метод MD5 лучше DES. И раз вообще заговорили про размер, то сравните длину получающихся хешей от работы этих алгоритмов.

Теперь, собственно говоря, сам процесс проверки пароля. Как Вы уже наверно поняли он сводится к простому сравнению, смотрим код:

#include <stdlib.h> #include <unistd.h> #include <sys/types.h>

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]