实验室给小 K 分配了一个高性能服务器的账户,为了不用重新配置 VSCode, Rclone 等小 K 常用的生产力工具,最简单的方法当然是把自己的家目录打包拷贝过去。
但是很不巧,对存放于小 K 电脑里的 Hackergame 2022 的 flag 觊觎已久的 Eve 同学恰好最近拿到了这个服务器的管理员权限(通过觊觎另一位同学的敏感信息),于是也拿到了小 K 同学家目录的压缩包。
然而更不巧的是,由于 Hackergame 部署了基于魔法的作弊行为预知系统,Eve 同学还未来得及解压压缩包就被 Z 同学提前抓获。
为了证明 Eve 同学不良企图的危害性,你能在这个压缩包里找到重要的 flag 信息吗?
公益广告:题目千万条,诚信第一条!解题不合规,同学两行泪。
VS Code 里的 flag
直接 grep
grep flag{ . -aR
img
Rclone 里的 flag
.config/rclone/rclone.conf [flag2] type = ftp host = ftp.example.com user = user pass = tqqTq4tmQRDZ0sT_leJr7-WtCiHVXSMrVN49dWELPH1uce-5DPiuDtjBUN3EI38zvewgN5JaZqAirNnLlsQ
瞄了眼 rclone 文档
rclone obscure
In the rclone config file, human-readable passwords are obscured. Obscuring them is done by encrypting them and writing them out in base64. This is not a secure way of encrypting these passwords as rclone can decrypt them - it is to prevent "eyedropping"
https://rclone.org/commands/rclone_obscure/
这里感觉就是通过 obscure 命令混淆后的密码,解码出来应该就是 flag 了
他文档说了 rclone 自己可以解密,那就找找哪里可以整了
rclone 论坛里的一个帖子 Rclone obscure password, what does it do? 也提到了这个
img
再看 obscure 这部分的源码 fs/config/obscure/obscure.go
这里面的 Reveal 函数便是解密的部分,可以看到用了硬编码的密钥
于是就直接改一改源码,调用 Reveal 函数把 pass 解密就完事了
Exp:
// Package obscure contains the Obscure and Reveal commands // package obscure package main
// crypt transforms in to out using iv under AES-CTR. // // in and out may be the same buffer. // // Note encryption and decryption are the same operation func crypt(out, in, iv []byte) error { if cryptBlock == nil { var err error cryptBlock, err = aes.NewCipher(cryptKey) if err != nil { return err } } stream := cipher.NewCTR(cryptBlock, iv) stream.XORKeyStream(out, in) return nil }
// Obscure a value // // This is done by encrypting with AES-CTR func Obscure(x string) (string, error) { plaintext := []byte(x) ciphertext := make([]byte, aes.BlockSize+len(plaintext)) iv := ciphertext[:aes.BlockSize] if _, err := io.ReadFull(cryptRand, iv); err != nil { return "", fmt.Errorf("failed to read iv: %w", err) } if err := crypt(ciphertext[aes.BlockSize:], plaintext, iv); err != nil { return "", fmt.Errorf("encrypt failed: %w", err) } return base64.RawURLEncoding.EncodeToString(ciphertext), nil }
// MustObscure obscures a value, exiting with a fatal error if it failed func MustObscure(x string) string { out, err := Obscure(x) if err != nil { log.Fatalf("Obscure failed: %v", err) } return out }
// Reveal an obscured value func Reveal(x string) (string, error) { ciphertext, err := base64.RawURLEncoding.DecodeString(x) if err != nil { return "", fmt.Errorf("base64 decode failed when revealing password - is it obscured?: %w", err) } if len(ciphertext) < aes.BlockSize { return "", errors.New("input too short when revealing password - is it obscured?") } buf := ciphertext[aes.BlockSize:] iv := ciphertext[:aes.BlockSize] if err := crypt(buf, buf, iv); err != nil { return "", fmt.Errorf("decrypt failed when revealing password - is it obscured?: %w", err) } return string(buf), nil }
// MustReveal reveals an obscured value, exiting with a fatal error if it failed func MustReveal(x string) string { out, err := Reveal(x) if err != nil { log.Fatalf("Reveal failed: %v", err) } return out }
func main() { fmt.Println(Reveal("tqqTq4tmQRDZ0sT_leJr7-WtCiHVXSMrVN49dWELPH1uce-5DPiuDtjBUN3EI38zvewgN5JaZqAirNnLlsQ")) } # go run exp.go flag{get_rclone_password_from_config!_2oi3dz1} <nil>
Use "rclone [command] --help" for more information about a command. Use "rclone help flags" for to see the global flags. Use "rclone help backends" for a list of supported services. Command reveal needs 1 arguments minimum: you provided 0 non flag arguments: [] # rclone reveal tqqTq4tmQRDZ0sT_leJr7-WtCiHVXSMrVN49dWELPH1uce-5DPiuDtjBUN3EI38zvewgN5JaZqAirNnLlsQ flag{get_rclone_password_from_config!_2oi3dz1}
def check(a): user_hash = sha256(('heilang' + ''.join(str(x) for x in a)).encode()).hexdigest() expect_hash = '76bd735ba6f0ca6213528caa474714a5322f668d6748e4214c79df4306ec9439' return user_hash == expect_hash
def get_flag(a): if check(a): t = ''.join([chr(x % 255) for x in a]) flag = sha256(t[:-16].encode()).hexdigest()[:16] + '-' + sha256(t[-16:].encode()).hexdigest()[:16] print("Tha flag is: flag{{{}}}".format(flag)) else: print("Array content is wrong, you can not get the correct flag.")
if __name__ == "__main__": get_flag(a)
按照题目要求把这个 | 改为批量赋值就完事了
Exp:
#!/usr/bin/env python3 import re from hashlib import sha256
a = [0] * 10000
with open('getflag.hei.py', 'r', encoding='utf-8') as f: s = f.read()
r = re.findall(r"a\[(.*)\] = (\d+)", s, re.M) for d in r: l = d[0].split(' | ') for i in l: a[int(i)] = int(d[1])
def check(a): user_hash = sha256(('heilang' + ''.join(str(x) for x in a)).encode()).hexdigest() expect_hash = '76bd735ba6f0ca6213528caa474714a5322f668d6748e4214c79df4306ec9439' return user_hash == expect_hash
def get_flag(a): if check(a): t = ''.join([chr(x % 255) for x in a]) flag = sha256(t[:-16].encode()).hexdigest()[:16] + \ '-' + sha256(t[-16:].encode()).hexdigest()[:16] print("Tha flag is: flag{{{}}}".format(flag)) else: print("Array content is wrong, you can not get the correct flag.")
if __name__ == "__main__": get_flag(a) # Tha flag is: flag{6d9ad6e9a6268d96-ba7e80b7a7fa0224}
public String flag() { var prefix = System.getenv(FLAG_PREFIX); var input = System.getenv(FLAG_SECRET) + ":" + this.raw; var digest = SHA256_DIGEST.digest(input.getBytes(StandardCharsets.UTF_8)); return String.format("flag{%s-%016x}", prefix, ByteBuffer.wrap(digest).getLong()); } }
private record State(Token token, int passed, int talented, double number, OptionalDouble previous) { private static final Random RNG = new SecureRandom();
private State update(XMLEventReader reader) throws XMLStreamException { var result = Optional.<State>empty(); var nameStack = new Stack<String>(); while (reader.hasNext()) { var event = reader.nextEvent(); if (event.isStartElement()) { var name = event.asStartElement().getName().getLocalPart(); nameStack.push(name); } if (event.isEndElement()) { if (nameStack.empty()) throw new XMLStreamException(); var name = event.asEndElement().getName().getLocalPart(); if (!name.equals(nameStack.pop())) throw new XMLStreamException(); } if (event.isCharacters()) { var path = List.of("state", "guess"); if (!path.equals(nameStack)) continue; if (result.isPresent()) throw new XMLStreamException(); try { var guess = Double.parseDouble(event.asCharacters().getData());
var isLess = guess < this.number - 1e-6 / 2; var isMore = guess > this.number + 1e-6 / 2;
var isPassed = !isLess && !isMore; var isTalented = isPassed && this.previous.isEmpty();
var newPassed = isPassed ? this.passed + 1 : this.passed; var newTalented = isTalented ? this.talented + 1 : this.talented; var newNumber = isPassed ? RNG.nextInt(1, 1000000) * 1e-6 : this.number; var newPrevious = isPassed ? OptionalDouble.empty() : OptionalDouble.of(guess);
result = Optional.of(new State(this.token, newPassed, newTalented, newNumber, newPrevious)); } catch (NumberFormatException e) { throw new XMLStreamException(e); } } } if (!nameStack.empty()) throw new XMLStreamException(); if (result.isEmpty()) throw new XMLStreamException(); return result.get(); } }
private static void dispatch(com.sun.net.httpserver.HttpExchange exchange) throws IOException { var method = exchange.getRequestMethod().toUpperCase(Locale.ROOT); switch (method + ' ' + exchange.getRequestURI().getPath()) { case "HEAD /", "HEAD /index.html", "HEAD /github-markdown.css" -> head(exchange); case "HEAD /GuessNumber.java", "HEAD /state" -> head(exchange); case "GET /", "GET /index.html" -> index(exchange); case "GET /github-markdown.css" -> style(exchange); case "GET /GuessNumber.java" -> source(exchange); case "POST /state" -> update(exchange); case "GET /state" -> collect(exchange); default -> bad(exchange); } }
小 X 作为某门符号计算课程的助教,为了让大家熟悉软件的使用,他写了一个小网站:上面放着五道简单的题目,只要输入姓名和题目答案,提交后就可以看到自己的分数。
点击此链接访问练习网站
想起自己前几天在公众号上学过的 Java 设计模式免费试听课,本着前后端离心(咦?是前后端离心吗?还是离婚?离。。离谱?总之把功能能拆则拆就对啦)的思想,小 X 还单独写了一个程序,欢迎同学们把自己的成绩链接提交上来。
总之,因为其先进的设计思想,需要同学们做完练习之后手动把成绩连接贴到这里来:
点击此链接提交练习成绩 URL
点击下方的「打开/下载题目」按钮,查看接收成绩链接的程序的源代码。
提示:
1. 不必输入自己真实的姓名;
2. 根据比赛规则,请勿分享自己的链接,或点击其他人分享的链接。
是 XSS 啊,给了 bot 源码
# Copyright 2022 USTC-Hackergame # Copyright 2021 PKU-GeekGame # # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. # # 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from selenium import webdriver import selenium import sys import time import urllib.parse import os # secret.py will NOT be revealed to players from secret import FLAG, BOT_SECRET
LOGIN_URL = f'http://web/?bot={BOT_SECRET}'
print('Please submit your quiz URL:') url = input('> ')
# URL replacement # In our environment bot access http://web # If you need to test it yourself locally you should adjust LOGIN_URL and remove the URL replacement source code # and write your own logic to use your own token to "login" with headless browser parsed = urllib.parse.urlparse(url) parsed = parsed._replace(netloc="web", scheme="http") url = urllib.parse.urlunparse(parsed)
print(f"Your URL converted to {url}")
try: options = webdriver.ChromeOptions() options.add_argument('--no-sandbox') # sandbox not working in docker options.add_argument('--headless') options.add_argument('--disable-gpu') options.add_argument('--user-data-dir=/dev/shm/user-data') os.environ['TMPDIR'] = "/dev/shm/" options.add_experimental_option('excludeSwitches', ['enable-logging'])
with webdriver.Chrome(options=options) as driver: ua = driver.execute_script('return navigator.userAgent') print(' I am using', ua)
print('- Now browsing your quiz result...') driver.get(url) time.sleep(4)
try: greeting = driver.execute_script(f"return document.querySelector('#greeting').textContent") score = driver.execute_script(f"return document.querySelector('#score').textContent") except selenium.common.exceptions.JavascriptException: print('JavaScript Error: Did you give me correct URL?') exit(1)
print("OK. Now I know that:") print(greeting) print(score)