[SCTF2019]Flag Shop——瞧,这是个新来的

[SCTF2019]Flag Shop

一.前言

这几天一直有事,导致之前的比赛的WP也没看,然后靶场这方面的学习也没推进下去,终于找了个时间的空挡做了道关于Ruby的模板注入。为了这道题我甚至抽出了我本就不多的时间去学了学Ruby的语法,话说学完Ruby之后,我的感触是他与Python等脚本语言很像,但是它的语法中字里行间都透露着Java等语言的关系。关于这次的SSTI题目,与其他的SSTI有着很大的不同。

二.正文

老样子,我们先看题目
[SCTF2019]Flag Shop——瞧,这是个新来的_第1张图片
题目的意思很简单就是我们要通过足够的jinkela来购买flag。但是我们发现我们的初始金额数是不够的,那怎么办呢?我们可以通过点击work选项来增加jinkela的数量,但是通过work所获得的jinkela数量有限。可是那又怎么样呢?我们只要点的多,我们总会达到那个数字的(bushi。
咳咳,咱们言归正传,我们扫描一下目录发现有robots.txt文件,我们访问一下看看。
[SCTF2019]Flag Shop——瞧,这是个新来的_第2张图片
我们直接访问一下/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("").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

我们发现这个源码中有JWT,我们抓一个包试试看。
[SCTF2019]Flag Shop——瞧,这是个新来的_第3张图片
我们拿着这个JWT去解密。
[SCTF2019]Flag Shop——瞧,这是个新来的_第4张图片
我们发现这个网站的jinkela数量是通过JWT进行传递的,所以我们只要能够得到这道题的加密密钥,从而伪造JWT,就行了。那么,我们该如何拿到密钥呢?从上面的整个代码中着重看如下代码。

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("").result

  end
end

这里涉及到了一些关于Ruby ERB模板注入的知识,具体的就不细说了,原理都差不多,只是语法的区别。
ERB模板注入链接
我们要通过<%=%>进行模板注入,我们想要得到SECRET但是在<%=%>中我们不能直接写SECRET,因为params[:name][0,7]的存在导致我们只能在模板内写入2个字符。但是幸运的是,Ruby为我们提供了预定义字符。

$’ 最后一次模式匹配中匹配部分之后的字符串

让我们看看运行到这句话之前的最后一个模式匹配在哪里?

unless params[:SECRET].nil?
    if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
      puts ENV["FLAG"]
    end

就是在匹配SECRET,这个预定义字符的作用是将匹配之后的字符进行返回。什么意思呢?我来举个例子

hello world //我设置匹配字符为e
llo world//这就是返回值

我们要想得到完整的SECRET,那就必须传进去一个空的SECRET,让最后的返回值是完整的。
所以我们如此构造payload

?name=<%=$'%>&do=<%=$' is working%>&SECRET=

[SCTF2019]Flag Shop——瞧,这是个新来的_第5张图片
结果如下
[SCTF2019]Flag Shop——瞧,这是个新来的_第6张图片
我们拿着这个密钥去伪造JWT
[SCTF2019]Flag Shop——瞧,这是个新来的_第7张图片
我们把这个伪造的JWT发过去。
[SCTF2019]Flag Shop——瞧,这是个新来的_第8张图片
我们看到服务端又给我返回了一个新的JWT,我们这次把他解密看看。
[SCTF2019]Flag Shop——瞧,这是个新来的_第9张图片
返回了flag.

三.后记

虽然这段忙的时间还没有过去,但是总有时间是可以腾出来的。之后就应该开始SSRF的学习了,SSTI也要告一段落了,应该和SQL一样把他们放在比赛中继续磨练了。总之当下的问题是先熬过这段艰苦的时光。

你可能感兴趣的:(SSTI,CTF,复现,ruby,安全漏洞)