RIPSTech presents
PHP Security Calendar 2017
Day 1 - Wish List
Can you spot the vulnerability?
|
|
The challenge contains an arbitrary file upload vulnerability in line 13. The operation
in_array()
is used in line 12 to check if the file name is a number.
However, it is type-unsafe because the third parameter is not set to 'true'. Hence, PHP will
try to type-cast the file name to an integer value when comparing it to the array
$whitelist (line 8). As a result it is possible to bypass the whitelist by prepending a value in the
range of 1 and 24 to the file name, for example "5backdoor.php". The uploaded PHP file then
leads to code execution on the web server.
Day 2 - Twig
Can you spot the vulnerability?
|
|
The challenge contains a cross-site scripting vulnerability in line 26. There are
two filters that try to assure that the link that is passed to the
<a>
tag is a genuine URL. First, the
filter_var()
function in line 22 checks if it is a valid URL.
Then, Twig's template escaping is used in line 10 that avoids breaking out of the
href
attribute.
The vulnerability can still be exploited with the following URL:
?nextSlide=javascript://comment%250aalert(1)
.
The payload does not involve any markup characters that would be affected by Twig's escaping.
At the same time, it is a valid URL for filter_var()
. We used a
JavaScript protocol handler, followed by a JavaScript comment introduced with
//
and then the actual JS payload follows on a newline.
When the link is clicked, the JavaScript payload is executed in the browser of the victim.
Day 3 - Snow Flake
Can you spot the vulnerability?
|
|
In this code are two security bugs.
A file inclusion vulnerability is triggered by the call of class_exists()
in line 8. Here, the existance of a user supplied class name is checked. This automatically invokes
the custom autoloader in line 1 in case the class name is unknown which will try to include unknown
classes. An attacker can abuse this file inclusion by using a path traversal attack. The lookup for
the class name ../../../../etc/passwd
will leak the passwd file.
The attack only works until version 5.3 of PHP.
But there is a second bug that also works in recent PHP versions. In line 9, the class
name is used for a new object instantiation. The first argument of its constructor is
under the attackers control as well. Arbitrary constructors of the PHP code base can be
called. Even if the code itself does not contain a vulnerable constructor, PHP's built-in
class SimpleXMLElement
can be used for an XXE attack
that also leads to the exposure of files. A real world example of this exploit can
be found in our
blog post.
Day 4 - False Beard
Can you spot the vulnerability?
|
|
This challenge suffers from an XML injection vulnerability in line 14. An attacker can
manipulate the XML structure and hence bypass the authentication. There is an attempt to prevent
exploitation in lines 8 and 9 by searching for angle brackets but the check can be bypassed
with a specifically crafted payload.
The bug in this code is the automatic casting of variables in PHP. The PHP built-in function
strpos()
returns the numeric position of the looked up character.
This can be 0
if the first character is the one searched for.
The 0 is then type-casted to a boolean
false
for the if
comparison which renders the overall constraint to true. A possible
payload could look like
user=<"><injected-tag%20property="&pass=<injected-tag>
.
Day 5 - Postcard
Can you spot the vulnerability?
|
|
This challenge suffers from a command execution vulnerability in line 31. The fifth
parameter of mail, in this case the variable $_POST['from']
,
is appended to the sendmail command that is executed to send out the email. It is not
possible to execute arbitrary commands here but it is possible to append arbitrary new parameters to sendmail.
This can be abused to create a PHP backdoor in the web directory through the log files of sendmail.
There are 2 insufficient protections in place that try to prevent successful exploitation. The method
sanitize()
first checks in line 3 if the e-mail address is valid. However, not all
characters that are necessary to exploit the security issue in mail()
are forbidden
by this filter. It allows the usage of escaped whitespaces nested in double quotes. In line 7 the e-mail address
gets sanitized with escapeshellarg()
. This would be sufficient if PHP would not escape
the fifth parameter internally with escapeshellcmd()
. Since it does escape the parameter
again, the escapeshellcmd()
allows an attacker to break out of the
escapeshellarg()
. More information, details, and a PoC can be found in our blog post
“Why mail() is dangerous in PHP”.
Day 6 - Frost Pattern
Can you spot the vulnerability?
|
|
This challenge contains a file delete vulnerability. The bug causing this issue is a non-escaped
hyphen character (-
) in the regular expression that is
used in the preg_replace()
call in line 21. If the hyphen is not
escaped, it is used as a range indicator, leading to a replacement of any character that
is not a-z or an ASCII character in the range between dot (46
)
and underscore (95
). Thus dot and slash can be used for
directory traversal and (almost) arbitrary files can be deleted, for example with the
query parameters action=delete&data=../../config.php
.
Day 7 - Bells
Can you spot the vulnerability?
|
|
This challenge suffers from a connection string injection vulnerability in line 4. It occurs
because of the parse_str()
call in line 21 that behaves very similar to register globals.
Query parameters from the referrer are extracted to variables in the current scope, thus we can
control the global variable $config
inside of
getUser()
in lines 5 to 8. To exploit this vulnerability we can
connect to our own MySQL server and return arbitrary values for username, for example with the
referrer
http://host/?config[dbhost]=10.0.0.5&config[dbuser]=root&config[dbpass]=root&config[dbname]=malicious&id=1
.
Day 8 - Candle
Can you spot the vulnerability?
|
|
This challenge contains a code injection vulnerability in line 4. Prior to PHP 7 the
operation preg_replace()
contained an eval modifier, short
e
. If the modifier is set, the second parameter (replacement) is
treated as PHP code. We do not have a direct injection point into the second parameter
but we can control the value of \\1
, as it references the matched
regular expression. It is not possible to escape out of the
strtolower()
call but since the referenced value is inside of
double quotes, we can use PHP’s curly syntax to inject other function calls.
An attack could look like this: /?.*={${phpinfo()}}
.
Day 9 - Rabbit
Can you spot the vulnerability?
|
|
This challenge contains a file inclusion vulnerability that can allow an attacker to
execute arbitrary code on the server or to leak sensitive files. The bug is in the
sanitization function in line 18. The replacement of the ../
string is not executed recursively. This allows the attacker to simply use the character
sequence ....//
or ..././
that
after replacement will end in ../
again. Thus, changing the
path to the included language file via path traversal is possible. For example, the
system's passwd file can be leaked by setting the following payload in the Accept-Language
HTTP request header:
.//....//....//etc/passwd
.
Day 10 - Anticipation
Can you spot the vulnerability?
|
|
This challenge contains a code injection vulnerability in line 12 that can be used by an
attacker to execute arbitrary PHP code on the web server. The operation
assert()
evaluates PHP
code and it contains user input. In line 1, all POST parameters are instantiated as global variables
by PHP's built-in function extract()
. This can lead to severe problems
itself but in this challenge it is only used for a variety of sources. It enables the attacker to
set the $pi
variable directly via POST Parameter. In line 8 there is a
check to verify if the input is numeric and if not the user is redirected to an error page via the
goAway()
function. However, after the redirect in line 5 the PHP
script continues running because there is no
exit()
call. Thus, user provided PHP code in the
pi
parameter is always executed, e.g.
pi=phpinfo()
.
Day 11 - Pumpkin Pie
Can you spot the vulnerability?
|
|
This challenge contains an PHP object injection vulnerability. In line 13 an attacker is able to pass user
input into the unserialize()
function by altering his cookie data.
There are two checks in line 11 and 12 that try to prevent the deserialization of objects.
The first check can be easily circumvented, for example by injecting an object into an array,
leading to a payload string beginning with
a:1:
instead of O:
.
The second check can be bypassed by abusing PHP's flexible serialization syntax.
It is possible to use the syntax O:+1:
to bypass this regex.
Finally, this means an attacker can inject an object of class Template
into the application.
After the serialized form is deserialized and the Template object is instantiated, its destructor
is called when the script terminates (line 31).
Now, the attacker controlled properties cacheFile
and
template
of the injected object are used to write to a file in line 21.
Thus, the attacker can create arbitraries files on the file system, for example a PHP shell in
the document root:
a:1:{i:0;O:%2b8:"Template":2:{s:9:"cacheFile";s:14:"/var/www/a.php";s:8:"template";s:16:"<?php%20phpinfo();";}}
More information about this attack can be found
in our blog posts.
Day 12 - String Lights
Can you spot the vulnerability?
|
|
There is a cross-site scripting vulnerability in line 13. This bug depends on the fact that
the keys of the $_GET
array (the GET parameter names) are not
sufficiently sanitized in the
code. Both the keys and the sanitized GET values are passed to the
href
attribute of the <a>
tag
as a concatenated string. The sanitizer htmlentities()
is used,
however, single quotes are not affected by default by this built-in function. Hence, an
attacker is able to perform an XSS attack against the
user, for example using the following query parameter that breaks the
href
attribute and appends an eventhandler with JavaScript code:
/?a'onclick%3dalert(1)%2f%2f=c
.
Note that the payload is within the parameter name, not the parameter value.
Day 13 - Turkey Baster
Can you spot the vulnerability?
|
|
Today's challenge contains a DQL (Doctrine Query Language) injection vulnerability in line 19.
A DQL injection is similar to a SQL injection but more limited, nonetheless the
where()
method of Doctrine is vulnerable. In line 13 and 14
sanitization is added to the input, however, the sanitizeInput()
method has a bug. First, it uses addslashes()
for escaping
relevant characters by adding a backslash \ infront of them. In this case if we pass
a \ as input, it get escaped to \\. But then, the substr()
function is used to truncate the escaped string. This enables an attacker to send a
string that is long enough that the escaped backslash is cut off and we are left with
a single \ at the end of the string. This will then break the WHERE statement and
allows the injection of own DQL syntax, for example the condition
OR 1=1
that is always true and bypasses the authentication:
user=1234567890123456789\&passwd=%20OR%201=1-
.
The resulting WHERE statement will look like
user = '1234567890123456789\' AND password = ' OR 1=1-'
in DQL. Note how the backslash confuses the quotes and allows to inject DQL into the password value.
The resulting query does not look valid because of the trailing slash. Fortunately,
Doctrine closes the last single quote on its own, so the resulting query looks like
OR 1=1-''
.
To avoid DQL injections always use bound parameters for dynamic conditions. Never try
to secure a DQL query with addslashes()
or similar functions.
Additionally, the password should be stored hashed in the database, for example in the BCrypt format.
Day 14 - Snowman
Can you spot the vulnerability?
|
|
This class is vulnerable to directory traversal because of mass assignment. The constructor
can be used to set arbitrary class attributes (line 11). By overwriting the
attribute $id
you
gain control over the first parameter of
file_put_contents()
in line 16. With the help of
../
it is possible to target arbitrary files on the system that are writable,
for example it can be used to create a PHP shell in the document root.
The values that are send to the class are incremented in line 11 and thus an
integer after the operation
is done. The incrementation happens after the assignment though, so the class
attribute contains the original value of $count
.
To avoid this security issue be vary careful when using reflection based on user input to set variables.
It is recommended to implement a white-list verfication that contains the names of all variables that can be modified.
A real world example of a vulnerability that is caused by mass assignment can be found
in our blog.
Day 15 - Sleigh Ride
Can you spot the vulnerability?
|
|
This challenge contains an open redirect vulnerability in line 6. The code takes the input
from the super global $_SERVER[‘PHP_SELF’]
and splits it at
the slash character (line 10).
Then the last part is taken and used to build the new URL that is passed into the
header()
function.
An attacker is able to inject his own URL by using url-encoded characters
that are decoded in line 5. A possible payload could look like
/index.php/http:%252f%252fwww.domain.com?redirect=1
.
This bug allows an attacker to redirect the user from the original site to a site of the
attackers will. The attacker then could do phishing, for example he could present a forged login
screen to grab the credentials of the user for the original site.
Day 16 - Poem
Can you spot the vulnerability?
|
|
This challenge contains two bugs that can be used together to inject data into the
open FTP connection. The first bug is the usage of $_REQUEST
in line 9 while only
sanitizing $_GET
and $_POST
in lines 14 to 16. $_REQUEST
is the combination of
$_GET
,
$_POST
, and $_COOKIE
but it is only a copy of the values, not a reference. Therefore
the sanitization of $_GET
, $_POST
,
and $_COOKIE
alone is not sufficient.
A real world example of a vulnerability that is caused by a similar confusion can be found
in our blog.
The second bug is the usage of the type-unsafe comparison
==
instead of ===
in line 25. This enables an
attacker to inject and execute new commands in the existing connection, for example
a delete command with the query string ?mode=1%0a%0dDELETE%20test.file
.
Day 17 - Mistletoe
Can you spot the vulnerability?
|
|
This challenge is supposed to be a fixed version of day 13 but it introduces new
vulnerabilities instead. The author tried to
fix the DQL injection by applying addslashes()
without
substr()
on the user name, and by hashing the password in line 13
using md5()
. Besides the fact that md5 should not be used to hash
passwords and that password hashes should not be compared this way, the second
parameter is set to true. This returns the hash in binary format. The binary hash can contain
ASCII characters that are interpreted by Doctrine.
In this case an attacker could use the value 128
as the password,
resulting in v�an���l���q��\
as hash.
With the backslash at the end the single quote gets escaped leading to an injection.
A possible payload could be ?user=%20OR%201=1-&passwd=128
.
To avoid DQL injections always use bound parameters for dynamic conditions. Never try to secure a
DQL query with addslashes()
or similar functions. Additionally, the
password should be stored in a secure hashing format, for example BCrypt.
Day 18 - Sign
Can you spot the vulnerability?
|
|
This challenge contains a bug in the usage of the openssl_verify()
function in line 5 that leads to an authentication bypass in line 7. The function has three return values:
1
if the signature is correct, 0
if the signature verification failed, and -1
if there
was an error while performing the verification. So if an attacker generates a valid
signature for the data using another algorithm than the one
pub_key.pem
is using, the openssl_verify()
function returns -1
which is casted to
true
automatically. To avoid this problem use the type-safe
comparison ===
to validate the return value
of openssl_verify()
, or consider using a different
library for cryptography.
Day 19 - Birch
Can you spot the vulnerability?
|
|
The ImageViewer
class is prone to remote command execution through the
size
parameter in line 17. The preg_replace()
call will purge almost any
non-digit characters. This is not sufficient though because the function
stripcslashes()
will not only strip slashes but it will also replace
C literal escape sequences with
their actual byte representation. The backslash character is untouched by the
preg_replace()
call allowing
an attacker to inject an octal byte escape sequence similar to
0\073\163\154\145\145\160\0405\073
.
The stripcslashes()
function will evaluate this input to
0;sleep 5;
which is concatenated into the
system command and finally executed in the attackers favor.
Day 20 - Stocking
Can you spot the vulnerability?
|
|
This challenge contains a server-side request forgery vulnerability. It allows an attacker to
perform requests on behalf of the attacked web server. Thus, servers can be reached that would
otherwise be not reachable for an external attacker. For example, this can be abused to perform
a port scan and to grab banners (e.g., version of the server) on an internal network that the
web server is part of. The exploitable parts
are the usage of file_get_contents()
with unfiltered user input in
line 14 and the printing of the
error message to the user in line 23. An attacker can request an internal URI like
?img=http://internal:22
and would get a response such as
failed to open stream: HTTP request failed! SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.2
if OpenSSH is running. Information like this can be used to prepare further attacks. Another
popular exploit scenario is the retrieval of sensitive AWS credentials when attacking an AWS cloud instance.
Besides that, filter_var()
also accepts file://
URLs, enabling an attacker to load local files.
Day 21 - Gift Wrap
Can you spot the vulnerability?
|
|
This challenge contains a command injection vulnerability in line 33. The developer declared
strict_types=1
in line 1 to ensure the the type hint in the validate function in line 7 throws a
TypeError
exception if a non-int
is passed to the class. Even with strict types enabled there is an bug with the usage of
array_walk()
which ignores
the strict typing and uses the default weak typing of PHP instead. An attacker can
therefore just append a command to the last parameter that is
executed in the system call. A possible payload could look like
?p[1]=1&p[2]=2;%20ls%20-la
.
Day 22 - Chimney
Can you spot the vulnerability?
|
|
The code snippet suffers from 4 vulnerabilities. First, the type-unsafe comparison operator is used in line 12
to compare the hashed password to a string. The string has the form of the scientific notation and
as a result it is interpreted as “zero to the power of X”, which is zero. So if we are able to generate
a zero-string for the hashed user input as well, the check compares zero to zero and succeeds. This hashes
are called “Magic Hashes” and a Google search reveals that the MD5 hash of the value
240610708
results in the desired properties. The code snippet calculates the MD5 hash of the password twice though, so it
is not possible to directly submit the value. Instead you have to exploit the second vulnerability: the first hash
is calculated on the server side but stored in a cookie on the client side. Thus the value
240610708
simply has to be directly injected into the password cookie.
There are two more vulnerabilities but they are not relevant for this challenge. First,
the comparison of the hashes
is vulnerable to timing attacks. To prevent this issue, the PHP function
hash_equals()
should be used for comparison.
Second, the PHP function md5()
is used to hash the password.
The MD5 algorithm is considered broken and it was not designed
for password hashing. Instead a secure password hashing algorithm like BCrypt should be used.
It should be noted that passwords also should not be hard coded but separated into a configuration file.
Day 23 - Cookies
Can you spot the vulnerability?
|
|
The LDAPAuthenticator
class is prone to an LDAP injection in line 24.
By injecting special characters
into the username it is possible to alternate the result set of the LDAP query. Although the
ldap_escape()
function is used to sanitize the input in lines 19 and 20, a wrong flag has been passed to the sanitize-calls resulting
in insufficient/incorrect sanitization. Therefore, in this particular example, the LDAP injection results
in an unauthenticated adversary bypassing the authentication mechanism by injecting the asterisk-wildcard
*
character as username and password to successfully login as an arbitrary user.
Day 24 - Nutcracker
Can you spot the vulnerability?
|
|
This challenge consists of a code snippet that was created by one of our team members for the Hack.lu
CTF Tournament. It makes heavy use of the next()
function and the
$GLOBALS
array.
The next()
function moves the internal array pointer
up by one. Combined with the $GLOBALS
array this allows us
to execute arbitrary code.
The payload has to be split up into 2 segments: First, a PHP function to execute, passed in via
$_COOKIE[‘GLOBALS’]
.
Second, parameters for the injected function, passed in via the file type of a sent file with the
same name as the called PHP function.
A more detailed write-up of the solution can be found
here.