## Материал для самостоятельного изучения Содержание: 1. Описание уязвимости 2. Примеры уязвимого кода 3. Примеры атак и уязвимых приложений ### Описание уязвимости По OWASP Top 10 уязвимость относится к `A03:2021 – Injection`. Возникает вследствии того, что пиложение может использовать напрямую или косвенно передачу команд в терминал или консоль операционной системы. Все современные консоли и терминалы поддерживают языки скриптования и запуск команд с использованием так называемых метасимволов. Метасимволы предоставляют пользлователю возможность объединять в логические цепочки команды, которые будут запускаться в ОС. :::danger Опасными символами являются: - & - && - | - || - && - ; - `` - $() - / - конструкции назначения новых переменных окружения ::: Узявимость опасна тем, что позволяет злоумышленнику запускать команды в операционной системе и начинать процесс эскалации привелегий или переконфигурации системы. ### Примеры уязвимого кода Уязвимость в коде может появиться из-за неверной обработки параметров и динамической генерации строки команды. Ниже приведены несколько примеров, на разных языках программирования: PHP ```php <?php $rootUname = $_GET['rootUname']; $array = array(); /* check PHP Safe_Mode is off */ if (ini_get('safe_mode')) { $array['phpSafeMode'] = '<strong><font class="bad">Fail - php safe mode is on - turn it off before you proceed with the installation</strong></font>br/>'; } else { $array['phpSafeMode'] = '<strong><font class="Good">Pass - php safe mode is off</strong></font><br/>'; } /* Test root account details */ $rootTestCmd1 = 'sudo -S -u ' . $rootUname . ' chmod 0777 /home 2>&1'; exec($rootTestCmd1, $cmdOutput, $err); $homeDirPerms = substr(sprintf('%o', fileperms('/home')), -4); if ($homeDirPerms == '0777') { $array['rootDetails'] = '<strong><font class="Good">Pass - root account details are good </strong></font><br/>'; } else { $array['rootDetails'] = '<strong><font class="bad">The root details provided have not passed: ' . $cmdOutput[0] . '</strong></font><br/>'; } // reset /home dir permissions $rootTestCmd2 = 'sudo -S -u ' . $rootUname . ' chmod 0755 /home 2>&1'; exec($rootTestCmd2, $cmdOutput, $err); echo json_encode($array); ``` C# ```C# using Microsoft.AspNetCore.Mvc; using System; using System.Diagnostics; namespace WebFox.Controllers { [Route("api/[controller]")] [ApiController] public class OsInjection : ControllerBase { [HttpGet("{binFile}")] public string os(string binFile) { Process p = new Process(); p.StartInfo.FileName = binFile; // Noncompliant p.StartInfo.RedirectStandardOutput = true; p.Start(); string output = p.StandardOutput.ReadToEnd(); p.Dispose(); return output; } } } ``` nodeJS ```javascript const express = require('express'); const router = express.Router() const { exec, spawn } = require('child_process'); router.post('/ping', (req,res) => { exec(`${req.body.url}`, (error) => { if (error) { return res.send('error'); } res.send('pong') }) }) router.post('/gzip', (req,res) => { exec( 'gzip ' + req.query.file_path, function (err, data) { console.log('err: ', err) console.log('data: ', data); res.send('done'); }); }) router.get('/run', (req,res) => { let cmd = req.params.cmd; runMe(cmd,res) }); function runMe(cmd,res){ // return spawn(cmd); const cmdRunning = spawn(cmd, []); cmdRunning.on('close', (code) => { res.send(`child process exited with code ${code}`); }); } module.exports = router ``` Java ```Java import testcasesupport.*; import javax.servlet.http.*; public class CWE78_OS_Command_Injection__getParameter_Servlet_10 extends AbstractTestCaseServlet { /* uses badsource and badsink */ public void bad(HttpServletRequest request, HttpServletResponse response) throws Throwable { String data; if (IO.staticTrue) { /* POTENTIAL FLAW: Read data from a querystring using getParameter */ data = request.getParameter("name"); } else { /* INCIDENTAL: CWE 561 Dead Code, the code below will never run * but ensure data is inititialized before the Sink to avoid compiler errors */ data = null; } String osCommand; if(System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) { /* running on Windows */ osCommand = "c:\\WINDOWS\\SYSTEM32\\cmd.exe /c dir "; } else { /* running on non-Windows */ osCommand = "/bin/ls "; } /* POTENTIAL FLAW: command injection */ Process process = Runtime.getRuntime().exec(osCommand + data); process.waitFor(); } /* goodG2B1() - use goodsource and badsink by changing IO.staticTrue to IO.staticFalse */ private void goodG2B1(HttpServletRequest request, HttpServletResponse response) throws Throwable { String data; if (IO.staticFalse) { /* INCIDENTAL: CWE 561 Dead Code, the code below will never run * but ensure data is inititialized before the Sink to avoid compiler errors */ data = null; } else { /* FIX: Use a hardcoded string */ data = "foo"; } String osCommand; if(System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) { /* running on Windows */ osCommand = "c:\\WINDOWS\\SYSTEM32\\cmd.exe /c dir "; } else { /* running on non-Windows */ osCommand = "/bin/ls "; } /* POTENTIAL FLAW: command injection */ Process process = Runtime.getRuntime().exec(osCommand + data); process.waitFor(); } /* goodG2B2() - use goodsource and badsink by reversing statements in if */ private void goodG2B2(HttpServletRequest request, HttpServletResponse response) throws Throwable { String data; if (IO.staticTrue) { /* FIX: Use a hardcoded string */ data = "foo"; } else { /* INCIDENTAL: CWE 561 Dead Code, the code below will never run * but ensure data is inititialized before the Sink to avoid compiler errors */ data = null; } String osCommand; if(System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) { /* running on Windows */ osCommand = "c:\\WINDOWS\\SYSTEM32\\cmd.exe /c dir "; } else { /* running on non-Windows */ osCommand = "/bin/ls "; } /* POTENTIAL FLAW: command injection */ Process process = Runtime.getRuntime().exec(osCommand + data); process.waitFor(); } public void good(HttpServletRequest request, HttpServletResponse response) throws Throwable { goodG2B1(request, response); goodG2B2(request, response); } /* Below is the main(). It is only used when building this testcase on * its own for testing or for building a binary to use in testing binary * analysis tools. It is not used when compiling all the testcases as one * application, which is how source code analysis tools are tested. */ public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { mainFromParent(args); } } ``` Есть несколько рекомандаций для митигации подобных уязвимостей: - PHP - использовать специальные функции для обработки данных, которые добавляются в командную строку: escapeshellarg() или escapeshellcmd() - JAVA - использование ProcessBuilder должно быть строго регламентировано правилом - команда и параметры должны быть описаны отдельно, а не одной строкой. Для остальных языков используется правило JAVA. ### Примеры атак и уязвимых приложений Для тестирования приложений с подобной уязвимостью атакующему приходится изучать алгоритм работы приложения. Основные данные для поиска: 1. Вывод интерфейса содержит целиком или частично данные, которые могут выдавать стандартные приложения ОС 2. Интерфейсы приложения, которые могут опираться на legacy код, который нужно запускать в терминале приложения. В качестве уязвимых приложений можно использовать ресурсы: - root-me - portswigger Academy - ringzer0team Представим в качестве примера несколько задач. Задание располагается по адресу: https://www.root-me.org/en/Challenges/Web-Server/PHP-Command-injection Приложение простое и содержит только функционал, который обращается в терминал, поэтому сразу пробуем через Repeater BurpSuite прставить тестовые данные: `| ls /` Наблюдаем результат: ![](https://i.imgur.com/tdtevaZ.png) Использование указанного тестового значения обусловлено тем, что данные, которые мы можем получить от добавленной команды не будет видно на экране. Поэтому результат оригинальной команды ping мы просто не выводим. Задание располагается по адресу: https://portswigger.net/web-security/os-command-injection/lab-simple Тестирование этого приложения отличается от предыдущего тем, что мы можем управлять поведением скрипта через 2 параметра. Иногда, чтобы запутать алгоритмы фильтрации злоумышленники могут разбивать данные для атаки. Например так: param1=;&param2=ls+/ Уязвимость этого приложения расположено в интерфейсе для запроса количества товаров на складе. Находим этот запрос в истории и пересылаем на repeater. для Теста будем использовать команду - `;ls /`: ![](https://i.imgur.com/RSjqoWk.png) Для автоматизации можно использовать вот [этот](https://github.com/portswigger/command-injection-attacker#user-interface) плагин. Найти его можно или в github или на вкладке extender. Нужно иметь в виду, что работа плагина возможна только в Pro версии Burp. Инициализация и работа плагина выполнена в виде отдельно активного скана. Мини инструкция: Для проведения тестирования с использованием плагина необходимо настроить точку, куда будут генерироваться тестовые данные. Для этого нужно выполнить следующие действия: 1. Перевести запрос на вкладку intruder: ![](https://i.imgur.com/BYgmrLO.png) 2. отметить позицию для проставления данных: ![](https://i.imgur.com/9f0tyx0.png) 3. Правой клавишей мыши запустить таску для тестирования уязвимости: ![](https://i.imgur.com/O0rUjP1.png) 4. Полученные данные можно наблюдать либо в таске на dashboard, либо следить за Issue. ![](https://i.imgur.com/Gj75LWD.png) Используемые тестовые данные полностью можно найти в `Task Details` и в настройках самого плагина. ### Полезные материалы - https://owasp.org/Top10/A03_2021-Injection/ - https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html - https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Command%20Injection