В этой статье сосредоточимся на уязвимостях, связанных с контролем доступа. Вы узнаете, как использовать JavaScript-код, загружаемый сайтами, для выявления потенциально уязвимых конечных точек. Кроме того, сможете освоить техники и методы обнаружения и устранения проблем с контролем доступа в веб-приложениях.
Прежде чем погружаться в детали этой уязвимости, настоятельно рекомендуется провести тесты в локальной среде, чтобы лучше понять обсуждаемые проблемы. Тестирование в реальном времени даст более четкое представление о том, как проявляется уязвимость, а также о шагах по ее устранению. Чтобы приступить к тестированию в локальной среде, воспользуйтесь предоставленной лабораторией. Полное руководство по настройке среды лаборатории находится здесь. Оно позволит самостоятельно моделировать уязвимости и отработать методы их устранения.
Установка лаборатории
git clone https://github.com/0xshdax/Lab-Web
cd Lab-Web
python3 app.py
Возвращаясь к теме, предположим, что вам поручили провести тестирование сайта на возможность проникновения и открыли доступ только к пользовательским аккаунтам с низким уровнем привилегий. В этом сценарии для выявления уязвимостей в управлении доступом можно использовать загруженный код JavaScript. На изображении ниже представлен дэшборд, на котором видно, что у пользователя есть только очень ограниченное меню, а именно: Reports («Отчеты») и Settings («Настройки»).
Уязвимость #1
На предыдущем изображении URL-адрес указывает на конечную точку /backend/dashboard, которая включает файл dashboard.js. Чтобы оценить скрипт, загрузите файл dashboard.js и выполните поиск вхождений ‘backend’ с помощью grep.
Видно, что в скрипте JavaScript есть и другие конечные точки, а именно: /backend/admin/users-management и /backend/admin/audit-logs. Попытки пользователей получить доступ к конечным точкам, находящимся в скрипте JavaScript, были успешны. Это указывает на уязвимость, называемую «broken access control» (нарушенный контроль доступа).
Как устранить эту проблему?
Согласно файлам app.py (строки с 39 по 49) и api.py (строки с 147 по 150) веб-лаборатории, очевидна недостаточная проверка доступа. Этот изъян позволяет обычным пользователям получать доступ к конечным точкам, которые должны быть доступны только администраторам. Следующие фрагменты кода иллюстрируют эти уязвимости:
# app.py
@app.route('/backend/admin/users-management')
def users_management():
if not user_is_logged_in():
return redirect(url_for('login_page'))
return render_template('admin/users-management.html')
[....]
# api.py
@api.route('/api/v2/users', methods=['GET'])
def get_users():
if not user_is_logged_in():
return jsonify({"message": "Access denied"}), 403
[....]
Чтобы решить проблему контроля доступа, необходимо обновить код с помощью соответствующих механизмов проверки доступа. Это гарантирует, что только авторизованные пользователи смогут получить доступ к определенной конечной точке. Например, можно реализовать проверку на стороне сервера для проверки ролей или разрешений пользователей перед предоставлением доступа к важным конечным точкам. Ниже приведен пример оптимизации логики контроля доступа:
# app.py
@app.route('/backend/admin/users-management')
def users_management():
if not user_is_logged_in():
return redirect(url_for('login_page'))
if 'role' not in session or session['role'] != 'admin':
return redirect(url_for('dashboard'))
return render_template('admin/users-management.html')
# api.py
@api.route('/api/v2/users', methods=['GET'])
def get_users():
if not user_is_logged_in():
return jsonify({"message": "Access denied"}), 403
if 'role' not in session or session['role'] != 'admin':
return jsonify({"message": "Access denied"}), 403
[....]
Внедрение надлежащей проверки доступа позволяет эффективно устранить уязвимости контроля доступа.
Уязвимость #2
В ходе тестирования также выявилось, что при доступе к меню Inventory («Запасы») загружается конечная точка /api/v2/inventory. Пользователь не имеет права вносить правки и получает сообщение «You do not have permission to edit this item» («У вас нет прав на редактирование этого элемента»).
Однако возвращение к ранее загруженному JavaScript-файлу и поиск конечной точки /api/v2/inventory позволяет обнаружить POST-запрос, который, скорее всего, используется для обновления данных.
// dashboard.js
const response = await fetch('/api/v2/inventory', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
id: id,
name: name,
product: product,
available: available,
category: category,
price: price
})
});
После тестового запуска с отправкой запроса на обновление данных выясняется, что запрос прошел успешно и данные могут быть обновлены.
Как устранить проблему?
Судя по строкам 126-139 файла dashboard.js, проверка доступа проводится, но только на стороне клиента.
// dashboard.js
const openEditPopup = (item) => {
if (userRole === 'admin') {
document.getElementById('item-id').value = item.id;
document.getElementById('name').value = item.name;
document.getElementById('product').value = item.product;
document.getElementById('available').value = item.available;
document.getElementById('category').value = item.category;
document.getElementById('price').value = item.price;
document.getElementById('edit-popup').style.display = 'flex';
} else {
alert('You do not have permission to edit this item.');
}
};
[....]
Судя по строкам 24-43 файла app.py, проверка доступа была реализована в недостаточной степени. Это приводит к тому, что пользователи с низким уровнем привилегий могут выполнять запросы, которые должны быть доступны только пользователям с ролью администратора.
# app.py
def update_inventory_data(id, name, product, available, category, price):
try:
with connect_db('inventory.db') as conn:
cursor = conn.cursor()
update_query = """
UPDATE inventory
SET name = ?, product = ?, available = ?, category = ?, price = ?
WHERE id = ?
"""
cursor.execute(update_query, (name, product, available, category, price, id))
conn.commit()
if cursor.rowcount == 0:
raise Exception("No record found with the provided ID.")
return True
[....]
Чтобы решить эту проблему, можно обновить код, включив в него надлежащую проверку доступа. Например:
# app.py
def update_inventory_data(id, name, product, available, category, price):
if 'role' not in session or session['role'] != 'admin':
return False
try:
with connect_db('inventory.db') as conn:
cursor = conn.cursor()
update_query = """
UPDATE inventory
SET name = ?, product = ?, available = ?, category = ?, price = ?
WHERE id = ?
"""
cursor.execute(update_query, (name, product, available, category, price, id))
conn.commit()
if cursor.rowcount == 0:
raise Exception("No record found with the provided ID.")
return True
[....]
Внедрение надлежащей проверки доступа позволяет значительно снизить риски, связанные с уязвимостями контроля доступом. Убедитесь, что каждая конечная точка снабжена соответствующей проверкой ролей пользователей для предотвращения несанкционированного доступа.
Надеюсь, эта статья поможет вам в тестировании на проникновение в веб-среду и даст четкое понимание того, как эффективно устранять уязвимости.
Читайте также:
- Интуитивно понятное объяснение конструкции Async/await в JavaScript
- TypeScript: расширение возможностей JavaScript
- 5 непростых вопросов по JavaScript
Читайте нас в Telegram, VK и Дзен
Перевод статьи Rafshanzani Suhada: JavaScript Analysis: Identifying and Exploiting Web Weaknesses