A “Simple” OS Command Injection Challenge
Introduction
This article will recount how I solved a custom-made Capture-The-Flag (CTF) challenge with an innovative solution that gave me an opportunity to give back to the open source repository — “swisskyrepo/PayloadsAllTheThings”.
TLDR; I made use of environment variables and Bash brace expansions to bypass a character blacklisting filter.
It all began one fine weekend when my friend wanted me to test his “simple” custom-made OS command injection CTF challenge in PHP. He had attempted to “secure” his vulnerable app with blacklisting techniques to thwart attackers from getting an easy injection.
What is OS Command Injection?
OWASP’s description provides one of the better definitions:
Command injection is an attack in which the goal is the execution of arbitrary commands on the host operating system via a vulnerable application. Command injection attacks are possible when an application passes unsafe user supplied data (forms, cookies, HTTP headers etc.) to a system shell.
The challenge
The application was a simple PHP ping webpage that accepts IP addresses utilising an underlying Linux “ping” command to test if the device is reachable.
The goal of the challenge was to obtain the two flags my friend had planted: one in the same directory, and the other in another directory.
Starting from the PING output, I derived that the underlying OS was most likely Linux as a Window’s OS “ping” starts with the word “Pinging” while Linux’s “ping” starts with the capitalised word “PING” as shown in Figure 2.
It quickly occurred to me that the challenge was not as simple as placing Linux commands behind a semi-colon. When attempting to perform a list command, it failed to return any output as seen in Figure 3.
The provided source code revealed multiple layers of filters to thwart an attacker with multiple common commands blacklisted as seen on line 21 of Figure 4. Without these commands, it was difficult to find the location of the flags and retrieve their content.
#1 — Bypass blacklisted commands
With a bit of Google “kungfu”, I easily found some useful bypasses documented on one of my favourite GitHub repositories — “swisskyrepo/PayloadsAllTheThings”.
After experimenting on my own local Kali Linux machine, I observed that single quotes without spaces in between led to string concatenation. This explained the bypasses from the repository.
Feeling confident with this new knowledge, I proceeded to bypass the blacklisted commands check and successfully listed files in the current directory, bringing me a step closer to the first flag.
#2 — Bypass blacklisted characters (whitespace)
The same directory contained a “user.txt” file which would have awarded me the first flag. However, I realised that commands requiring more than a single word could not be injected. In Figure 8, the long format option for “ls” failed because it required 2 words — “ls” and “-la”.
Revisiting the source code revealed that the developer had blocked many special characters, such as whitespace on line 17 of Figure 9. Whitespace is crucial to split commands into words and with this filter, I was limited to single-word commands.
So I went back to the same GitHub repository and was intrigued by some interesting whitespace bypasses I found. The third bypass utilised the character ‘$’ (see Figure 10), that fortunately was not found within the blacklisted character set.
Curious to find out how the bypass works, I read further on other bypass techniques and came across online Bash documentation about “$IFS” as a delimiter. It described “$IFS” as a special shell environment that splits up commands into words in Bash.
Using the “$IFS” environment variable, I successfully bypassed the whitespace requirement and retrieved the first flag. The contents of the file provided a hint to the location of the second flag.
#3 — Bypass blacklisted characters (slashes)
Working towards retrieving the second flag proved to not be as simple as bypassing the “/” character found within the blacklisted character set! There were no convenient bypass techniques for slashes online. Other bypass techniques required the use of the “\”, “|”, or “<” characters, all of which were blacklisted.
I began investigating the environment variables found on the server and realised that the flag was located in the home directory of the user. This led to my next question:
“Could I use the $HOME directory to read the flag?”
My attempt was unsuccessful as the “$HOME” environment variable lacked an additional “/” character to read the “root.txt” file.
Undeterred, I spent a few hours researching when an idea sparked dawned upon me: what if I tried using string manipulation! Upon conducting further research on Bash, I learnt that Bash allows string splicing to extract substring from a variable. Since the “$HOME” directory contains “/”, I could therefore splice the “/” character from the variable itself!
Combining all the knowledge I acquired, I pieced together the entire path to the second flag!
I successfully retrieved the second flag and overcame the challenge!
My payload impressed my friend; he had not expected me to bypass the “/” character because there were no public write ups on this bypass.
Contribution back to the community
We then discussed on how my payload could be useful in bypassing character filter and decided to share my experience in the PayloadAllTheThings repository to benefit the Pen-testing community.
# Tips for defenders
When filtering special characters to solve OS command injection, remember to include special characters such as quotes and “;${}”, that can be cleverly manipulated to craft bypasses.
# Other Defences for command injection attacks
1. The best defence is to avoid calling the OS system directly.
2. Depending on your program’s context, validate and restrict inputs to good, whitelisted, and known inputs. In the Ping example, one can use regular expressions to ensure the inputs fits the format of IPv4 or IPv6. All mismatched inputs should otherwise be rejected.
3. Use language-specific secure functions. For example, in PHP use escapeshellarg() or escapeshellcmd() instead of exec(), system(), passthru().
Find out more here:
https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html
Conclusion
I find that such challenges broaden my knowledge of command injections and strengthen my thought process when facing difficulties. In general, we may know how command injections work. In reality, they pose a different challenge when you exploit one that claims to be “well-secured” by filters.