Ето как за първи път успях да хакна уеб сайт

Оригиналът е на Дейвид Гилбертсън (David Gilbertson), като доколкото може е запазена стилистиката на автора. Дейвид не е специалист по информационна безопасност и SQL инжектиране. Неговата молба е да бъдем по-снизходителни към неговата наивност

3
3461

Съвсем наскоро намерих интересна уязвимост в един уеб сайт, позволяваща за произволен потребител да се зададе произволна парола. Това си е интересно нали?

На мен ми се стори много забавно и реших, че от това ще се получи интересна статия. Ето я и нея, а кой е конкретният сайт няма да разкривам.

Мнозина от нас знаят, че когато отвориш някой сайт в инструментариума за разработчици на браузъра, изследването на кода и мрежовите запитвания може да бъде много интересно. И понякога виждаш, че тук нещо не е наред. Изобщо не е наред. И аз така преглеждах кода на потребителския профил в един уеб сайт и забелязах, че когато избираш дали да ти се изпраща или да не се изпраща уведомление, уеб страницата използва следната заявка:

/api/users?email=no

Помислих си, интересно, а дали тук не са направили някоя глупост? Може би си струва да опитам SQL инжектиране?

Потърсих в уеб пространството информация за „xkcd little bobby tables„, за да освежа паметта си относно SQL инжектирането – харесва ми идеята и се заех да изследвам какво ще стане.

В мрежовия раздел на инструментариума на Chrome копирах тази заявка (Copy > Copy as fetch) и я поставих във вид на снипет – малък фрагмент, който може да възпроизведе същата заявка:

fetch('https://blah.com/api/users', {
  credentials: 'include',
  headers: {
    authorization: 'Bearer blah',
    'content-type': 'application/x-www-form-urlencoded',
    'sec-fetch-mode': 'cors',
    'x-csrf-token': 'blah',
  },
  referrer: 'https://blah.com/blah',
  referrerPolicy: 'no-referrer-when-downgrade',
  body: 'email=no', // < -- The bit we're interested in
  method: 'POST',
  mode: 'cors',
});

Всичко написано по-долу е посветено на реда с командата body – именно тя е своеобразния транспорт за изпращането на тази инструкция към сървъра.

Първоначално се опитах да променя своята фамилия, като задам някакво значение в полето на хипотетичната колонка lastName, като просто налучквам едно от стандартните имена в тези бази данни:

{
  // ...
  body: `email=no', lastName='testing`
}

Нищо интересно не се получи. Малко по-късно пробвах същото с last_name, а след това опитах със surname и хоп! – страницата замени моята фамилия с думата „testing“.

Това бе направо смайващо. Винаги съм считал SQL инжектирането за нещо като книжна легенда. За непосветените в тези неща ще обясня какво представлява постигнатият от мен резултат. Мисля че на сървъра протича нещо от този сорт:

const userId = someSessionStore.userId;
const email = request.body.email;

const sql = `UPDATE users SET email = '${email}' WHERE id = '${userId}'`;

Уверен съм че техният сървър използва PHP, но аз не владея този програмен език и ще пиша примерите на JavaScript. Освен това, нямам откъде да знам подробности за SQL таблицата. Нямам си понятие дали тази таблица се нарича user или users, а може би user_table, но това не е и толкова важно.

Забелязах, че когато изпращам моя потребителски ID 1234 и задавам email=no, то SQL заявката става следната:

UPDATE users SET email = 'no' WHERE id = '1234'

Но ако добавя surname = ‘testing’, SQL заявката ще бъде валидна и ще изглежда доста хитро:

UPDATE users SET email = 'no', surname = 'testing' WHERE id = '1234'

Да напомня, че изпращам заявките от фрагмента код, който съм записал в инструментариума за разработчици, но в същото време съм в страницата с моя профил. Така че от този момент полето surname от тази уеб страница може да се счита за (HTML елемента <inрut>) малък stdout, в който може да се записва информация, задавайки различни значения за колоната surname в базата данни.

Стана ми интересно, дали ще успея да копирам данните от друга колона в колоната surname?

В началото не разбирах какво точно трябва да направя с този SQL, а и не знаех каква база данни се използва на сървъра. Така че за да направя всяка една стъпка ми бяха необходими двадесетина минути търсене в Мрежата и дълго чесане по главата, понеже все не слагах кавичките където трябва. Странно как не сринах цялата база данни.

Да се копират данните от една колона в друга се оказа малко по-сложно, понеже исках да изпратя заявка (предположих, че има колона password), подобна на следното:

UPDATE users SET email = 'no', surname = password WHERE id = '1234'

Обърнете внимание, че в кода около password няма кавички. Може би някои знаят, но суперсъвременният конструктор на запитванията би трябвало да изглежда по следния начин:

const sql = `UPDATE users SET email = '${email}' WHERE id = '${userId}'`;

Тоест при опита да се зададе no’, surname = password, полученият ред от символи няма да бъде валидна SQL заявка. А на мен ми трябва инжектираният ред да стане втората част на заявката и всичко, което се намира под нея, да се игнорира, По-точно, трябва по някакъв начин да изпратя „WHERE“ и „;“ в края на SQL израза, както и знакът # за началото на коментар, за да може информацията вдясно от него да се игнорира. Да, знам, че обясненията ми са ужасни.

Накратко, изпратих следния ред:

{
  // ...
  body: `email=no', surname = password WHERE username = '[email protected]'; #`
}

А към базата данни ще бъде изпратено следното:

UPDATE users SET email = 'no', surname = password WHERE username = '[email protected]';
# WHERE id = '1234'

Обърнете внимание, че базата данни ще игнорира id = ‘1234’, понеже тази част се намира след знака за коментар (#). Не ви ли се струва забраната на коментари в SQL заявките е един нелош начин за защита от некадърно написания код?

Надявах се, че избраната от мен парола [email protected] ще се появи в текстов вид след фамилията, но вместо това получих 00fcdde26dd77af7858a52e3913e6f3330a32b31.

Това ме разочарова, но не ме учуди и аз продължих с опити да копирам хеша на моята парола в колонката за парола на друг потребител.

Да обясня за начинаещите. Когато създаваш акаунт в някой уеб сайт, и задаваш нова парола, като например [email protected], той се превръща в хеш – нещо подобно на 00fcdde26dd77af7858a52e3913e6f3330a32b31, който се записва в базата данни. По този хеш няма как да се определи оригиналната парола (или поне така казват).

Когато следващият път се логвате и въведете паролата [email protected], сървърът отново изчислява хеша и го сравнява със записания в базата данни хеш. По този начин се проверява съответствието, без в базата данни да се записва оригиналната парола.

Това означава, че ако искам да дам на някой друг паролата [email protected], аз трябва да поставя в колоната password на този потребител значението 00fcdde26dd77af7858a52e3913e6f3330a32b31.

Нищо работа.

Отворих друг браузър, създадох нов потребител с друга електронна поща и най-напред проверих, дали и на него мога да задам други данни. Обнових свойството body:

{
  // ...
  body: `email=no', surname = 'WOOT!!' WHERE username = '[email protected]'; #`
}

Стартирах кода, обнових страницата на този потребител и, дявол да го вземе, сработи! Сега той имаше фамилията „WOOT!!“, която е моминското име на моята баба.

След това се пробвах за този нов потребител да задам и парола:

  // ...
  body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b31' WHERE username = '[email protected]'; #`
}

И знаете ли какво стана?

Нищо не се получи. Сега дори нямах достъп до втория акаунт. Оказа се, че съм допуснал две грешки, за откриването на които ми бяха необходими часове. Специалистите по информационна безопасност, попаднали на тази статия, навярно вече се хилят, понеже са разбрали какво става. Със сигурност се присмиват на глупавия, който пише свои „експлойти“, подходящи за първата страница на книгата „Хакване за най-малките„.

Е, все пак потърсих в Глобалната мрежа информация за „password hash“ и забелязах, че повечето хешове са по-дълги от моя 00fcdde26dd77af7858a52e3913e6f3330a32b31. Изглежда че някъде го отрязват.

Опитах се вмъкна някакъв текст в полето surname и видях, че има лимит от 40 символа (добре е, че са задали атрибута maxlength за <inрut>, за да се получи съответствие с ограничението, зададено в базата данни).

Сега ме интересуваха само първите 40 символа на хеша, който би могъл да бъде много по-дълъг. Потърсих в интернет информация за „sql substring“ и малко след това изпратих на сървъра следната заявка:

{
  // ...
  body: `email=no', surname = SUBSTRING(password, 30, 1000) WHERE username = '[email protected]'; #`
}

Започнах с 30, за да се убедя, че първите 10 символа навярно се добавят към последните 10. А може би от последните 9. Или 11.

Символите наистина се добавят и след обединяването на редовете получих хеш от 64 символа. Отново пробвах да го копирам за новия потребител:

{
  // ...
  body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b3121a61bce915cc6145fc44453' WHERE username = '[email protected]'; #`
}

И знаете ли какво стана?
Да де, сигурно сте се досетили, понеже споменах две грешки.

Отново потърсих в глобалната мрежа за „best practices database password“ и бързо си спомних за метода на посоляване на хеша.

Използването на сол за хеша означава добавяне на допълнителни символи към основната поредица. Тоест, ако за паролата [email protected] на някой потребител се получава един хеш, то друг потребител със същата парола ще има съвсем друг хеш. И хешът от единия потребител няма как да се използва за друг потребител със същата парола.

Изглежда умно, но не е чак толкова. Във всичките тези примери и таблици просто са поставили още една колона с име salt. А дали това не означава, че трябва да копирам данните от полетата на две колонки, а не от една? Нима това не изглежда като втори катинар, който може да се отключи със същия ключ?

Промених заявката с надеждата да копирам полето на колонката, която би могла да се нарича salt, в полето на колонката surname:

{
  // ...
body: `email=no', surname = salt WHERE username = '[email protected]'; #`
}

В полето на фамилията се върна разбъркан ред от символи, което всъщност е добър знак. За получаването на това, което мислех за 64 символна сол, отново използвах SUBSTRING.

Всичко бе готово. Имах хеша на паролата и солта, използвана при неговото създаване, които трябва да бъдат копирани в съответните полета на другия потребител. И изпратих своята последна за тази вечер мрежова заявка:

fetch('https://blah.com/api/users', {
  credentials: 'include',
  headers: {
    authorization: 'Bearer blah',
    'content-type': 'application/x-www-form-urlencoded',
    'sec-fetch-mode': 'cors',
    'x-csrf-token': 'blah',
  },
  referrer: 'https://blah.com/blah',
  referrerPolicy: 'no-referrer-when-downgrade',
  body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b3121a61bce915cc6145fc44453', salt = '8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52' WHERE username = '[email protected]'; #`,
  method: 'POST',
  mode: 'cors',
});

Получи се! Сега мога да се логна във втория акаунт са паролата от първия акаунт.
Не е ли безумно?

Направих много проби и грешки. Но когато си избера да хакна истински потребител, първо ще получа неговите хеш и сол и ще си ги запиша на моя компютър. След това ще заменя посоления хеш със своя, както е описано по-горе, ще се логна в чуждия акаунт и моментално ще заменя хеша и солта с техните оригинални значения. Трябва ми само за секунда да променя чуждата парола или да направя нещо друго от този род и едва ли някой ще забележи.

Теоретично разбира се. Всъщност, никога не бих постъпил така.

Може би ви е интересно да разберете дали това не е измислена история? Не е. Естествено, променени са някои подробности, за да няма обвинения, но всичко останало си е точно така, както е описано. Ясно е, че съобщих за тази уязвимост на собственика на сайта.

Но все се питам, дали това не бе просто късмета на начинаещия. Това буквално е наистина първият уеб сайт, на който пробвам SQL инжектиране и сякаш всичко бе приготвено специално за мен, сякаш имах изпит по хакване за малки деца.

Хакнатият сайт не е голям (34 718 потребители). Това е платена услуга, която не е интересна за хакерите от висок клас. И все пак останах поразен, че е възможно да се направи подобно нещо.

Сега се запалих на тема информационна безопасност. Някак си обединих две мои любими занимания: писането на код и хулиганството. И след като потърсих в Google за „information security salaries Australia„, мисля че си намерих нова работа.

Благодаря, че дочетохте всичко това.

3
ДОБАВИ КОМЕНТАР

avatar
1 Коментари
2 Отговори на коментарите
0 Последователи
 
Коментарът с най-много реакции
Най-горещият коментар
  Абонирай се  
нови стари оценка
Извести ме за
bure_s_barut
bure_s_barut

Този сайт да не би да е писан в кръжок за начинаещи по програмиране? Всички входни данни от потребителя се проверяват без изключение на ниво сървърен код!

Сторм
Сторм

Тоя е хакнал НАП човек, са ни казва как… 😀 😀 😀

Ебачев
Ебачев

Вижте как в БАН след поредната финансова инжекция ще градят център по „квантова компетентност“ и ще „предсказват природни бедствия и терористични актове“ използвайки СТРУГ и КОЛОННА БОРМАШИНА!!!

blitz.bg/lyubopitno/evrika/stranen-bg-ured-shche-predskazva-opasni-apokalipsisi-snimki_news699768.html