Skip to content

passengers 1

Dmitry Zubarev edited this page Mar 15, 2018 · 4 revisions

Spacebury Craft набирает экипаж для полёта в космос

Условие

Корпорация Spacebury Craft, созданная известным бизнесменом и изобретателем Иваном Ланном, открыла набор экипажа на первый запуск пилотируемого корабля за пределы солнечной системы. Чтобы заявить о своём желании принять участие в экспедиции, достаточно воспользоваться системой регистрации с открытым исходным кодом.

Все заявки будут рассмотрены специальным комитетом Spacebury. Члены комитета выберут 50 человек, которые станут кандидатами на участие в полёте. Их ждёт череда серьёзных испытаний на пригодность к полёту и месяцы тренировок. По словам самого Ивана Ланна, итоговый экипаж будет состоять из 10 человек. Подробности испытаний, которые предстоит пройти 50 счастливчикам, пока не разглашаются.

Примечание: для доступа к системе следует использовать программу nc: nc passengers.contest.qctf.ru 50001. При заполнении заявки нужно воспользоваться токеном Your_Token_Right_Here

Решение

Краткая информация о системе

Из условия мы знаем, что имеем дело с какой-то системой регистрации пассажиров.

Сразу заметим, что есть структура, представляющая пассажира:

struct passenger {
    int id;
    unsigned char is_frozen;

    unsigned char age;
    unsigned char is_crewman;
    unsigned char token[7];

    unsigned char country[100];
    unsigned char first_name[100];
    unsigned char last_name [100];

    void (*on_unfreeze_ptr)(struct passenger*);
    void (*on_freeze_ptr)(struct passenger*);
    void (*info_ptr)(struct passenger*);
};

Тут есть пара важных полей: age - 1 байт, is_crewman - 1 байт и token - 7 байтов. Также, указатели на 3 метода вывода информации (если вы не знаете, что такое указатели, то можете почитать о них, например, здесь).

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

switch (choice) {
    case 31337:
        if (applier != NULL && crewman_enter(applier))
            crewman_menu();
        break;
    case 1:
        ...
    case 2:
        ...
    case 3:
        ...
    case 4:
        ...
    case 5:
        ...
}

При вводе 31337 происходит проверка подающего заявку на принадлежность к экипажу. Если всё пройдёт успешно, то мы попадём в меню для члена экипажа (важно понимать, что пассажир и член экипажа — это разные привилегии). Давайте посмотрим как происходит проверка в crewman_enter.

int crewman_enter(struct passenger *pass) {
    printf("\n[*] Entering as a member of the ship crew...\n");
    printf("[*] Checking is running...\n");
    sleep(2);
    if (pass->is_crewman == 1) {
        for (int i = 0; i < CREWMANS_COUNT; i++) {
            if (ship[i]->is_crewman && !strcmp(used_tokens[i], pass->token))
                printf("[+] Done. Welcome!\n");
                return 1;
        }
    }
    printf("[-] Invalid credentials.\n");
    return 0;
}

Сперва проверяется, что выставлен флаг принадлежности экипажу, затем происходит итерирация по членам экипажа и проверка токенов. Если токен подающего заявку совпадает с чьим-либо, то выводится [+] Done. Welcome! и считается, что он может войти в другой терминал. Стоит оговориться, что на сервере исполняемый файл не совпадал с приведённым исходным кодом. В частности, не было проверки на токен.

Уязвимость

Посмотрим что проиходит в подаче заявки:

int input_age(long *real_age) {
    char age[MAX_LEN];

    input_string(age, AGE_LEN);
    *real_age = parse_long(age);

    if (*real_age <= 18) {
        printf("[-] Invalid age, try again!\n\n");
        return 0;
    } else return 1;
}


struct passenger* apply() {
    long real_age = 0;
    ......
    ......
    ......
     printf("     Age: ");
    if (!input_age(&real_age)) return NULL;
    
    struct passenger *pass = create_passenger(firstname, lastname, country, real_age);
    ......
    ......
}

Из приведённого кода видно, что real_age, который мы передаём, имеет размер 8 байт (т.к. его тип — long).

Стоит посмотреть, как происходит инициализация пассажира:

struct passenger* create_passenger(char *first_name, char *last_name, char *country, long age) {
    struct passenger *passng = malloc(sizeof(struct passenger));
    .......
    .......
    .......
    long *_age = (long *) &passng->age;
    *_age = age;

    return passng;
}

Теперь можно соединить всё в единое целое. Мы вводим число real_age размером в 8 байт, которое отправляется в create_passenger в качестве аргумента age. В функции passng->age — это указатель на char * (то есть на 1 байт). Затем, passng->age кастуется к указателю на long * и после этого мы записываем 8 байт real_age по этому указателю. Таким образом, мы заменяем 8 байт в структуре пассажира, тем самым изменяя значения полей age, is_crewman и token. То есть, мы можем выставить is_crewman = 1 и токен одного из членов экипажа.

Эксплуатация

Используя приведённую выше уязвимость мы можем задать age = 8806289263452881168 = "\x10\x01rim96z", после этого is_crewman = 1; token = rim96z. Затем введём 31337 в choice и попадём в терминал члена экипажа и выведем secret information, в которой лежит флаг.