关于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语法)%>
- <%=直接输出变量值或运算结果%>
1 | require 'erb' |

如果x可控,即可实现常见的ssti
1 | require 'erb' |

还可以进行文件读取
1 | require 'erb' |

枚举当前类的可用方法
1 | require 'erb' |

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模式下,此变量保存扩展名 |
| NIL | 0本身 |
| ENV | 当前环境变量 |
| RUBY_VERSION | Ruby版本 |
| RUBY_RELEASE_DATE | 发布日期 |
| RUBY_PLATFORM | 平台标识符 |
一些常用payload
1 | <%= 7 * 7 %> |
靶场演示
- [SCTF2019]Flag Shop
/filebak
1 | require 'sinatra' |
我们可以判断出主要的漏洞点在于以下代码中

1 | get "/work" do |
我们是想得到jwt的secret
而在这段代码我们可以看到,ERB会对传入密钥进行正则匹配且对于其存在一个字数限制,当传入的参数do和params相等的话会弹出#{params[:name][0,7]} working successfully!')</script>
而在ruby的全局变量中,存在这么一个全局变量:$’– 最后一次匹配后的内容,所以我们就可以利用这个对匹配的字符进行读取得到secret
payload
1 | 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 %>用于评估表达式并将结果呈现在页面上
所以我们不妨尝试一下,看他能不能把我们想要的打印出来
1 | <%=7*7%> |

七七四十九,很明显是可以打印出来的,那么我们就知道这message参数我们是可控的,这里就存在ERB模板注入问题
而在ERB文档中,依旧存在我们喜闻乐见的system()函数

那么我们不妨whoami一下看看

确实可以运行,当前用户为carlos
那么这道题目解决就需要删除特定文件morale.txt即可,根据题目与目录翻找得到文件路径
1 | /home/carlos/morale.txt |

删除文件
1 | <%=system("rm%20/home/carlos/morale.txt")%> |

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