Root-Me Writeup — PHP Eval
Hello everybody, back to the penetration testing exercise. This time I exercised at root-me. This challenge is talking about a vulnerability in eval function implementation in the PHP language. Basically, sometimes, we have established some firewalls like regular expression filtration, but unfortunately, it still can be bypassed. In this challenge, we will talk about how we bypass a regular-expression firewall.
Challenge Introduction
The challenge is as follows. There is a web-app that contains the eval function. Eval function definition is as follows referring to PHP official site.
eval — Evaluate a string as PHP code
This challenge also gives us the source code. The source code is as follows.
Now that is the introduction, we will begin the explanation.
Information Gathering
As always, every time we are going to do a challenge, we need to know the target you want to achieve. Trust me, after several challenges I have done, it is always unsolvable if you do not know what to achieve. Even when you have the skills and tools. So getting information about the target is the thing you have to do in the first place.
Source Code Analysis
In the source code, we can see the process flow.
Here is the explanation.
- Line 1–6. They are just some HTML tags.
- Line 8–10,23. This is the form tag, which means, this is our entry to exploit the system.
- Line 11–22. This is the PHP code.
- Line 13. It checks whether we have submitted the form.
- Line 14. It checks whether the data we supplied does NOT contain “A-Z”, “a-z”, and “ ` “. It is actually a good firewall because it is really hard to do command injection without letters.
- Line 15–17. If it does not contain any of that, it will do the eval function. The eval executes the print function and execute any syntax we passed onto it. For example, we give input like 1+1, the result will be 2.
- Line 18–20. This is the error message if the data contains letters and the backtick.
- Line 23–25. These are the closing tags of the documents.
Application Analysis
Strategy
Keep in mind, this is a combination of experience and knowledge. Do not feel bad if you do not understand where this strategy comes from.
The challenge says we need to read the content of the .passwd file. As we can see, this is an os-related challenge which means we hope that we can execute several PHP functions that interact with OS. Some of them are:
- system. usage = system(“shell command”) / system(“ls -al”).
- shell_exec. usage = shell_exec(“shell command”) / shell_exec(“ls -al”).
Okay, the command we will be using is ready which is system(“cat .passwd”) or shell_exec(“cat .passwd”). Now the next question is how to bypass this command through the firewall that allows no letters.
This is where the experience comes to play. This is worth remembering. We can create characters from the XOR function, for example, “(” XOR “[” = “S”. Great! now we can create characters without letters.
Now our strategy is ready. We will construct ‘system(“cat .passwd”)’ command from the combination of XOR symbols. Fingers crossed, this will execute as we hope.
The Execution
The pain point is to build the command in XOR-ed symbols. We will use programming now. I create a program that converts a string into XOR-ed symbols.
Lol, that is long, this is the simpler one. The long one is to show you the struggle and the imperfection.
We will breakdown our code one by one.
The explanation of the snippet is as follows:
- Line 1. We establish the list of strings we want to convert to make the process more efficient.
- Line 2. We establish the charset we are going to use. I do not use several characters such as ‘ ` ’ and ‘ ” ’ because it will raise a conflict with the firewall and the string separator (because the system is using ‘ “ ’ as the separator………. or at least because I face error when doing it :) ).
- Line 4–21. This is when the brute force happens. I loop all the requested string and convert them one by one.
- Line 5. This is to set up the empty variable we will be using as our placeholder.
- Line 7–20. Loop all the characters of the string because we have to encode them one by one. Because I find it really difficult to use the whole string xor-ed with another string, for example “!@#$%&*” XOR “{}|[]\”. It is difficult because it raises an error of miss XOR value. Because of that we will xor them one by one and use concatenation to combine all the xor values. This is why we need to loop these characters one by one.
- Line 9. We set the flag to False. This value will be changed if we find the XOR combination.
- Line 10–18. We loop through every character in our symbol’s charset and try the combination one by one until we found the necessary combination.
- Line 12–14. We check if the XOR value of both symbols (in ASCII number, btw. That is what the ord function for) equals the character we want (in ASCII number as well).
- Line 13. We format our output to be readable. If you notice, I put a leading dot. It is on purpose because the goal is to concatenation every character to be a string and concatenation in PHP is by using a dot.
- Line 14. If the combination is found, then set the flag to True hence it does not need to continue the loop and can continue to the next character instead.
- Line 15–18. If the combination found, skip all the brute force processes and continue to the next character.
- Line 19–20. Just in case the combination is not found, just put the original value into the placeholder.
- Line 21. We print the output.
Okay, that is the code. Now we will try our first exploit. Our target expression to be executed is “print system(‘id’);”. The “print” part has been handled by the challenge, we need to supply the system(‘id’). We execute and this is what I get.
system('id') = ('('^'[').('$'^']').('('^'[').(')'^']').('%'^'@').('-'^'@').'('.('['^'|').(')'^'@').('$'^'@').('['^'|').')'
Do you see it? We are concatenating every character produced by XOR-ed symbols. Now we send it to the challenge page. Yay, it works!
HAHAHAHAHAH it does work though we pass some string and it is executed, but, that is not what we want, is it? We want the “system(‘id’)” command is actually parsed and executed. This is where another experience comes to play. There are several ways to call a function in PHP.
- function(). This is the most common way.
- $variable = “functionName”;
$variable(); - (“functionName”)()
We know that the first one does not work. The second one is also will not work for us because there is an assignment and we do not want to call the defined function. We want to call the PHP legacy function. That is why we need to try the third one.
We are about to call system(“command”). In the third form, it looks like (“system”)(“command”). Now we break down again.
- We can easily pass the parentheses because it is not letters
- We need to convert the “system” string into symbols.
- We also need to convert the command “cat .passwd” into symbols.
And we input it to our program before and it resulted like below:
system = ('('^'[').('$'^']').('('^'[').(')'^']').('%'^'@').('-'^'@')
cat .passwd = ('#'^'@').('!'^'@').(')'^']').('['^'{').'.'.('+'^'[').('!'^'@').('('^'[').('('^'[').('('^'_').('$'^'@')
Now we combine them into a string.
(system)(cat .passwd) = ((‘(‘^’[‘).(‘$’^’]’).(‘(‘^’[‘).(‘)’^’]’).(‘%’^’@’).(‘-’^’@’))((‘#’^’@’).(‘!’^’@’).(‘)’^’]’).(‘[‘^’{‘).’.’.(‘+’^’[‘).(‘!’^’@’).(‘(‘^’[‘).(‘(‘^’[‘).(‘(‘^’_’).(‘$’^’@’))
Put that back on the site, and…….. take a deep breath of relief.
Closing
This is an exciting challenge. It really challenges my strategy. At the end of the day, experience and knowledge of programming is necessary and it is a really good thing to learn about. That’s all my friend! See you! To GOD be all the glory! Soli Deo Gloria.