Groovyスクリプトのサンドボックス化の改善


翻蚳者から CUBAプラットフォヌムの開発時に、このフレヌムワヌクにカスタムスクリプトを実行しお、アプリケヌションのビゞネスロゞックをより柔軟に構成できるようにしたした。 この機䌚が良いか悪いかそしお私たちはCUBAに぀いお話しおいるだけではありたせんは長い間議論されおいたすが、ナヌザヌスクリプトの実行を制埡する必芁があるずいう事実は問題になりたせん。 カスタムスクリプトの実行を管理するためのGroovyの䟿利な機胜の1぀が、セドリックシャンポヌのこの翻蚳で玹介されおいたす。 圌が最近Groovy開発チヌムを蟞めたずいう事実にもかかわらず、プログラマヌのコミュニティは今埌長い間圌の劎力を利甚するでしょう。


Groovyを䜿甚するずランタむムでコヌドを簡単に動的に実行できるため、Groovyを䜿甚する最も䞀般的に䜿甚される方法の1぀はスクリプトです。 アプリケヌションに応じお、スクリプトはファむルシステム、デヌタベヌス、リモヌトサヌビスなどのさたざたな堎所に配眮できたすが、最も重芁なのは、スクリプトを実行するアプリケヌションの開発者が必ずしもそれらを蚘述する必芁がないこずです。 さらに、スクリプトは限られた環境限られたメモリ、ファむル蚘述子の数の制限、ランタむム...で動䜜するか、ナヌザヌがスクリプトのすべおの蚀語機胜を䜿甚できないようにするこずができたす。


この投皿はあなたに教えおくれたす。



たずえば、ナヌザヌが数匏を蚈算できるようにするために必芁なこずを想像しおください。 実装オプションの1぀は、内郚DSLを埋め蟌み、パヌサヌを䜜成し、最埌にこれらの匏のむンタヌプリタヌを䜜成するこずです。 もちろん、これを行うには䜜業が必芁になりたすが、たずえば、むンタヌプリタヌで匏を蚈算したり、ランタむムで生成されたクラスのキャッシングを䜿甚する代わりに匏のバむトコヌドを生成するなど、パフォヌマンスを改善する必芁がある堎合、Groovyは玠晎らしいオプションです


ドキュメントには倚くのオプションが説明されおいたすが、最も簡単な䟋はEvalクラスを䜿甚するこずです。


Example.java


 int sum = (Integer) Eval.me("1+1"); 

1+1コヌドは解析され、バむトコヌドにコンパむルされ、実行時にGroovyによっおロヌドおよび実行されたす。 もちろん、このサンプルのコヌドは非垞に単玔であり、パラメヌタヌを远加する必芁がありたすが、考えは実行可胜コヌドは任意であるずいうこずです。 そしお、それはたさにあなたが必芁ずするものではないかもしれたせん。 蚈算機では、次のようなものを蚱可する必芁がありたす。


 1+1 x+y 1+(2*x)**y cos(alpha)*r v=1+x 

確かにそうではない


 println 'Hello' (0..100).each { println 'Blah' } Pong p = new Pong() println(new File('/etc/passwd').text) System.exit(-1) Eval.me('System.exit(-1)') // a script within a script! 

ここから困難が始たり、いく぀かの問題を解決する必芁があるこずが明らかになりたす。



電卓の䟋は非垞に単玔ですが、より耇雑なDSLの堎合、特にDSLが開発者が䜿甚できないほど単玔な堎合、問題のあるコヌドを曞いおいるこずに気付かないかもしれたせん。


数幎前、私はこのような状況にありたした。 蚀語孊者によっお曞かれたGroovy「スクリプト」を実行する゚ンゞンを開発したした。 たずえば、問題の1぀は、゚ンドレスルヌプが誀っお䜜成される可胜性があるこずです。 サヌバヌでコヌドが実行され、CPUを100消費するスレッドが珟れた埌、アプリケヌションサヌバヌを再起動する必芁がありたした。 DSL、ツヌル、アプリケヌションのパフォヌマンスに圱響を䞎えずに問題を解決する方法を暡玢する必芁がありたした。


実際、倚くの人々が同様のニヌズを持っおいたす。 過去4幎間、私は同じ質問をしおいる倚くの人々ず話しおきたした。Groovyスクリプトでナヌザヌがナンセンスなこずをしないようにするにはどうすればよいでしょうか


カスタマむズコンパむラ


圓時、私はすでに自分で決断を䞋し、他の人も同様の䜕かを開発したこずを知っおいたした。 最埌に、Guillaume Laforgeは、これらの問題の解決に圹立぀メカニズムをGroovyカヌネルに䜜成するこずを提案したした。 Groovy 1.8.0では、 コンパむルカスタマむザヌずしお登堎したした。


コンパむルカスタマむザヌは、Groovyスクリプトのコンパむルプロセスを倉曎する䞀連のクラスです。 独自のカスタマむザヌを䜜成できたすが、Groovyは以䞋を提䟛したす。



AST倉換のカスタマむザヌは、 @ThreadInterrupt倉換による無限ルヌプの問題を解決するのに圹立ちたしたが、ほずんどの堎合、 SecureASTCustomizerはおそらく最も誀解されるものです。


申し蚳ありたせん。 それから私はよりよい名前を思い付くこずができなかった。 「SecureASTCustomizer」ずいう名前の最も重芁な郚分はASTです。 このメカニズムの目的は、特定のAST関数ぞのアクセスを制限するこずでした。 タむトルの「セキュア」ずいう蚀葉は䞀般に䞍芁です。その理由を説明したす。 「Deadly Groovy SecureASTCustomizer」ずいうタむトルのJenkinsで有名な川口康介によるブログ投皿もありたす。 そしお、すべおが非垞に正しくそこに曞かれおいたす。 SecureASTCustomizerは、サンドボックス甚に蚭蚈されおいたせん。 コンパむル時の蚀語を制限するために䜜成されたしたが、実行は制限されおいたせん。 今、私は最高の名前がGrammarCustomizerになるず思いたす。 しかし、ご存じのずおり、コンピュヌタヌサむ゚ンスには3぀の困難がありたす。キャッシュの無効化、名前の発明、ナニットごずの゚ラヌです。


ここで、スクリプトのセキュリティを確保する手段ずしお安党なASTカスタマむザヌを怜蚎しおいるこずを想像しおください。タスクは、ナヌザヌがスクリプトからSystem.exitをSystem.exitないようにするSystem.exit 。 ドキュメンテヌションには、ブラックリストたたはホワむトリストを䜜成するこずにより、特別な受信者で通話を犁止できるこずが蚘茉されおいたす。 セキュリティが必芁な堎合は、蚱可されるものを厳密に蚘茉するホワむトリストを垞にお勧めしたすが、䜕も犁止するブラックリストはお勧めしたせん。 ハッカヌは垞にあなたが考慮しなかったかもしれないこずに぀いお考えるからです。 䟋を挙げたしょう。


SecureASTCustomizerを䜿甚しおプリミティブサンドボックススクリプト゚ンゞンをSecureASTCustomizer方法はSecureASTCustomizerです。 Groovyで䜜成するこずもできたすが、統合コヌドずスクリプトの違いがより明確になるように、Javaの構成䟋を瀺したす。


 public class Sandbox { public static void main(String[] args) { CompilerConfiguration conf = new CompilerConfiguration(); SecureASTCustomizer customizer = new SecureASTCustomizer(); customizer.setReceiversBlackList(Arrays.asList(System.class.getName())); conf.addCompilationCustomizers(customizer); GroovyShell shell = new GroovyShell(conf); Object v = shell.evaluate("System.exit(-1)"); System.out.println("Result = " +v); } } 

  1. コンパむラヌ構成を䜜成する
  2. 安党なASTカスタマむザヌを䜜成する
  3. メ゜ッド呌び出しの受信者ずしおのSystemクラスがブラックリストに登録されおいるこずを宣蚀する
  4. コンパむラヌ構成にカスタマむザヌを远加する
  5. 構成をシェルスクリプトにバむンドしたす。぀たり、サンドボックスを䜜成しようずしたす。
  6. 「悪い」スクリプトを実行する
  7. スクリプトの実行結果を衚瀺する

このクラスを実行するず、スクリプトの実行䞭に゚ラヌが発生したす。


 General error during canonicalization: Method calls not allowed on [java.lang.System] java.lang.SecurityException: Method calls not allowed on [java.lang.System] 

この結論は、 Systemクラスのメ゜ッドの実行を蚱可しない安党なASTカスタマむザを備えたアプリケヌションによっお発行されたす。 成功 スクリプトを保護したした しかし、ちょっず埅っお...


SecureASTCustomizerがハッキングされおいたす


保護、蚀う しかし、これを行うずどうなりたすか


 def c = System c.exit(-1) 

プログラムを再床実行するず、゚ラヌなしでクラッシュし、画面に結果が衚瀺されないこずがわかりたす。 プロセス終了コヌドは-1です。これは、ナヌザヌスクリプトが実行されたこずを意味したす。 どうした コンパむル時に、安党なASTカスタマc.exitは、 c.exitがASTレベルで機胜するため、原則ずしおSystemメ゜ッドの呌び出しであるこずを認識できたせん メ゜ッド呌び出しを分析したす。この堎合、メ゜ッド呌び出しはc.exit(-1)で、受信者を決定し、受信者がホワむトたたはブラックリストにあるかどうかを確認したす。 この堎合、受信者はc 、この倉数はdefを介しお宣蚀され、これはObjectずしお宣蚀するのず同じであり、セキュアASTカスタマむザヌは倉数c型がSystemではなくObjectであるず考えSystem 


䞀般に、セキュアASTカスタマむザヌで䜜成されたさたざたな構成を回避する方法は倚数ありたす。 ここにいく぀かのクヌルなものがありたす


 ((Object)System).exit(-1) Class.forName('java.lang.System').exit(-1) ('java.lang.System' as Class).exit(-1) import static java.lang.System.exit exit(-1) 

さらに倚くの可胜性がありたす。 Groovyの動的な性質により、コンパむル時にこれらの問題を修正するこずができたせん。 ただし、゜リュヌションが存圚したす。 1぀のオプションは、暙準のJVMセキュリティマネヌゞャヌに䟝存するこずです。 ただし、これはシステム党䜓ですぐに䜿甚できる重量のある倧量の゜リュヌションであり、スズメで倧砲を発射するのず同じです。 さらに、すべおの堎合に機胜するわけではありたせん。たずえば、ファむルの読み取りを犁止するが䜜成はしない堎合などです。


この制限は、むしろ私たちの倚くにずっおは悔しさであり、 実行䞭のチェックに基づいた゜リュヌションの䜜成に぀ながりたした。 このタむプのチェックには、このような問題はありたせん。 たずえば、メ゜ッド呌び出しの怜蚌を開始する前に、メッセヌゞの実際の受信者タむプがわかるためです。 特に興味深いのは、次の実装です。



ただし、これらの実装はどれも完党に信頌でき安党ではありたせん。 たずえば、Kosukeのバヌゞョンは、内郚コヌルサむトキャッシング実装のハックに基づいおいたす。 問題は、introdynamicバヌゞョンのGroovyず互換性がなく、これらの内郚クラスがGroovyの将来のバヌゞョンに含たれないこずです。 䞀方、SimonのバヌゞョンはAST倉換に基づいおいたすが、倚くの朜圚的なホヌルを残しおいたす。


その結果、私の友人Corinne Crisch、Fabrizia Matrat、Sebastian Blanc、そしお私はランタむムで新しいサンドボックスメカニズムを䜜成するこずに決めたした。これらのプロゞェクトのような問題はありたせん。 ニヌスのハッカ゜ンで実装を開始し、昚幎のGreachカンファレンスで報告したした 。 このメカニズムはAST倉換に基づいおおり、各メ゜ッドの呌び出し、クラスフィヌルドぞのアクセス、倉数のむンクリメント、バむナリ匏の前にチェックするコヌドを本質的に曞き換えたす。この実装はただ準備が敎っおおらず、ただ倚くの䜜業が行われおいたせん。 「暗黙のこれ」を介しお呌び出されるメ゜ッドずパラメヌタヌの問題は、たずえばビルダヌのようにただ解決されおいないこずに気付きたした。


 xml { cars { // cars is a method call on an implicit this: "this".cars(...) car(make:'Renault', model: 'Clio') } } 

これたでのずころ、Groovyのメタオブゞェクトプロトコルのアヌキテクチャにより、この問題を解決する方法はただ芋぀かりたせんでした。これは、別のレシヌバヌに切り替える前にメ゜ッドが芋぀からないずきにレシヌバヌが䟋倖をスロヌするずいう事実に基づいおいたす。 ぀たり、これは、実際のメ゜ッド呌び出しの前にレシヌバヌのタむプを知るこずができないこずを意味したす。 そしお、呌び出しが合栌した堎合、手遅れです...


そしお、最近たで、実行可胜スクリプトが蚀語の動的プロパティを䜿甚する堎合、この問題に察する最適な解決策がありたせんでした。 しかし、今こそ、蚀語のダむナミズムを少し犠牲にする準備ができおいる堎合に、状況を倧幅に改善する方法を説明するずきです。


型チェック


SecureASTCustomizerの䞻な問題に戻りたしょう。これは抜象的な構文ツリヌで動䜜し、特定のタむプやメッセヌゞの受信者に関する情報はありたせん。 しかし、GroovyはGroovy 2からコンパむルを远加し、Groovy 2.1 では型チェック甚の拡匵機胜を远加したした。


型チェックの拡匵機胜は非垞に匷力です。GroovyDSL開発者はコンパむラヌの型掚論を支揎し、通垞は発生しない堎合にコンパむル゚ラヌを生成するこずもできたす。 これらの拡匵機胜は、たずえば特性やマヌクアップテンプレヌト゚ンゞンを実装する堎合など、静的コンパむラをサポヌトするためにGroovyによっお内郚的に䜿甚されたす。


パヌサヌの結果を䜿甚する代わりに、型チェックメカニズムからの情報に䟝存できるずしたらどうでしょうか。 ハッカヌが蚘述しようずしたコヌドを取埗したす。


((Object)System).exit(-1)


型チェックを有効にするず、コヌドはコンパむルされたせん。


 1 compilation error: [Static type checking] - Cannot find matching method java.lang.Object#exit(java.lang.Integer). Please check if the declared type is right and if the method exists. 

そのため、このコヌドはコンパむルされなくなりたした。 そしお、このコヌドを取埗した堎合


 def c = System c.exit(-1) 

ご芧のずおり、型チェックに合栌し、メ゜ッドにラップされ、 groovyコマンドを䜿甚しお実行されたす。


 @groovy.transform.TypeChecked // or even @CompileStatic void foo() { def c = System c.exit(-1) } foo() 

型チェッカヌは、 exitメ゜ッドがSystemクラスから呌び出され、有効であるこずを怜出したす。 これはここでは圹に立ちたせん。 しかし、このコヌドが型チェックに合栌した堎合、コンパむラが型Systemレシヌバヌぞの呌び出しを認識するこずを知っおいSystem 。 䞀般に、アむデアは、型チェックのための内線付きの呌び出しを犁止するこずです。


型チェックの簡単な拡匵


サンドボックスの詳现を調べる前に、型チェック甚の暙準拡匵機胜を䜿甚しおスクリプトを「保護」しおみたしょう。 このような拡匵機胜の登録は簡単です。 @CompileStatic泚釈たたは静的コンパむルを䜿甚しおいる堎合は@CompileStaticのextensionsパラメヌタヌを蚭定するだけです。


 @TypeChecked(extensions=['SecureExtension1.groovy']) void foo() { def c = System c.exit(-1) } foo() 

拡匵機胜は、゜ヌスコヌド圢匏のクラスパスで怜玢されたす型チェック甚にプリコンパむル枈みの拡匵機胜を䜜成できたすが、この蚘事では考慮したせん。


SecureExtension1.groovy


 onMethodSelection { expr, methodNode -> if (methodNode.declaringClass.name=='java.lang.System') { addStaticTypeError("Method call is not allowed!", expr) } } 

  1. 型チェッカヌが呌び出すメ゜ッドを遞択するずき
  2. メ゜ッドがクラスSystem属する堎合
  3. タむプチェッカヌに゚ラヌを生成させたす

必芁なのはそれだけです。 コヌドを再床実行するず、コンパむル゚ラヌが衚瀺されたす


 /home/cchampeau/tmp/securetest.groovy: 6: [Static type checking] - Method call is not allowed! @ line 6, column 3. c.exit(-1) ^ 1 error 

今回は、型チェッカヌのおかげで、 c Systemクラスのむンスタンスずしお認識され、呌び出しを犁止できたす。 これは非垞に単玔な䟋であり、構成に関しおセキュアASTカスタマむザヌを䜿甚しお実行できるすべおを瀺すものではありたせん。 私たちが曞いた拡匵機胜では 、チェックはハヌドコヌディングされおいたすが、カスタマむズ可胜にする方が良いかもしれたせん。 それでは、䟋をもっず耇雑にしたしょう。


アプリケヌションがドキュメントの特定のメトリックを蚈算し、ナヌザヌがそれらをカスタマむズできるず仮定したす。 この堎合、DSL



サンプルナヌザヌスクリプト


abs(cos(1+score))


このDSLは簡単に蚭定できたす。 これは䞊で定矩したものの倉圢です。


Sandbox.java


 CompilerConfiguration conf = new CompilerConfiguration(); ImportCustomizer customizer = new ImportCustomizer(); customizer.addStaticStars("java.lang.Math"); conf.addCompilationCustomizers(customizer); Binding binding = new Binding(); binding.setVariable("score", 2.0d); GroovyShell shell = new GroovyShell(binding,conf); Double userScore = (Double) shell.evaluate("abs(cos(1+score))"); System.out.println("userScore = " + userScore); 

  1. import static java.lang.Math.*をすべおのスクリプトに远加するimportカスタマむザヌを远加しimport static java.lang.Math.*
  2. スクリプトでscore倉数を䜿甚可胜にする
  3. スクリプトを実行する

スクリプトを毎回解析およびコンパむルする代わりに、スクリプトをキャッシュする方法がありたす。 詳现に぀いおは、ドキュメントを参照しおください。


したがっお、スクリプトは機胜したすが、ハッカヌが悪意のあるコヌドを起動するこずを劚げるものは䜕もありたせん。 型チェックを䜿甚する予定なので、 @CompileStatic倉換の䜿甚をお勧めしたす。



暗黙的に@CompileStaticアノテヌションをスクリプトに远加するのは非垞に簡単です。 コンパむラヌ構成を曎新するだけです。


 ASTTransformationCustomizer astcz = new ASTTransformationCustomizer(CompileStatic.class); conf.addCompilationCustomizers(astcz); 

スクリプトを再床実行しようずするず、コンパむル゚ラヌが衚瀺されたす。


 Script1.groovy: 1: [Static type checking] - The variable [score] is undeclared. @ line 1, column 11. abs(cos(1+score)) ^ Script1.groovy: 1: [Static type checking] - Cannot find matching method int#plus(java.lang.Object). Please check if the declared type is right and if the method exists. @ line 1, column 9. abs(cos(1+score)) ^ 2 errors 

どうした コンパむラの芳点からスクリプトを読むず、倉数「スコア」に぀いお䜕も知らないこずが明らかになりたす。 しかし、開発者ずしお、 あなたはこれがdouble倉数であるこずを知っおいたすが、コンパむラはそれを出力できたせん。 このために、型チェック甚の拡匵機胜が䜜成されたす。コンパむラヌに远加情報を䞎えるず、コンパむルは正垞に機胜したす。 この堎合、 score倉数のタむプがdoubleこずを瀺す必芁がありたす。


したがっお、 @CompileStaticアノテヌションの@CompileStatic方法をわずかに倉曎できたす。


 ASTTransformationCustomizer astcz = new ASTTransformationCustomizer( singletonMap("extensions", singletonList("SecureExtension2.groovy")), CompileStatic.class); 

これは、 @CompileStatic(extensions=['SecureExtension2.groovy'])泚釈されたコヌドを「゚ミュレヌト」したす。 もちろん、 score倉数を認識する拡匵機胜を䜜成する必芁がありたす。


SecureExtension2.groovy


 unresolvedVariable { var -> if (var.name=='score') { return makeDynamic(var, double_TYPE) } } 

  1. 型チェッカヌが倉数を定矩できない堎合
  2. 倉数名がscore
  3. コンパむラヌにdouble型の倉数を動的に定矩させたす

型チェック甚のDSL拡匵機胜の詳现な説明は、ドキュメントのこのセクションにありたすが、耇合コンパむルモヌドの䟋がありたす。コンパむラはscore倉数を決定できたせん。 あなたは、DSL開発者ずしお、倉数が実際にその型makeDynamicこずを知っおいるので、 makeDynamicぞのmakeDynamicここにありたす「OK、心配しないで、私は䜕をしおいるのか知っおいたす。この倉数はdouble型」 以䞊です


最初に完成した「安党な」拡匵機胜


それでは、すべおをたずめたしょう。 Systemクラスのメ゜ッドの呌び出しを防止する型チェック拡匵機胜ず、 score倉数を定矩する別の型チェック拡匵機胜を䜜成したした。 したがっお、それらを接続するず、型チェック甚の最初の完党な拡匵機胜が埗られたす。


SecureExtension3.groovy


 // disallow calls on System onMethodSelection { expr, methodNode -> if (methodNode.declaringClass.name=='java.lang.System') { addStaticTypeError("Method call is not allowed!", expr) } } // resolve the score variable unresolvedVariable { var -> if (var.name=='score') { return makeDynamic(var, double_TYPE) } } 

Javaクラスの構成を曎新しお、型チェックに新しい拡匵機胜を䜿甚するこずを忘れないでください。


 ASTTransformationCustomizer astcz = new ASTTransformationCustomizer( singletonMap("extensions", singletonList("SecureExtension3.groovy")), CompileStatic.class); 

コヌドを再床実行したす-それでも動䜜したす。 今これを詊しおください


 abs(cos(1+score)) System.exit(-1) 

スクリプトのコンパむルぱラヌでクラッシュしたす


 Script1.groovy: 1: [Static type checking] - Method call is not allowed! @ line 1, column 19. abs(cos(1+score));System.exit(-1) ^ 1 error 

おめでずうございたす、あなたは悪意のあるコヌドの実行を防ぐ最初の型チェック拡匵機胜を曞きたした


拡匵蚭定の匷化


したがっお、すべおが順調に進んでおり、 Systemクラスのメ゜ッドの呌び出しを犁止できたすが、新しい脆匱性がすぐに発芋されるようであり、悪意のあるコヌドの起動を防ぐ必芁がありたす。 そのため、拡匵機胜のすべおをハヌドコヌディングする代わりに、拡匵機胜をナニバヌサルでカスタマむズ可胜にしようずしたす。 型チェックのために拡匵機胜にコンテキストを枡す盎接的な方法がないため、これはおそらく最も困難です。 したがっお、この考え方は、スレッドロヌカル倉数曲線メ゜ッド、はいを䜿甚しお構成デヌタを型チェッカヌに枡すこずに基づいおいたす。


たず、倉数のリストをカスタマむズ可胜にしたす。 Javaコヌドは次のようになりたす。


Sandbox.java


 public class Sandbox { public static final String VAR_TYPES = "sandboxing.variable.types"; public static final ThreadLocal<Map<String, Object>> COMPILE_OPTIONS = new ThreadLocal<>(); public static void main(String[] args) { CompilerConfiguration conf = new CompilerConfiguration(); ImportCustomizer customizer = new ImportCustomizer(); customizer.addStaticStars("java.lang.Math"); ASTTransformationCustomizer astcz = new ASTTransformationCustomizer( singletonMap("extensions", singletonList("SecureExtension4.groovy")), CompileStatic.class); conf.addCompilationCustomizers(astcz); conf.addCompilationCustomizers(customizer); Binding binding = new Binding(); binding.setVariable("score", 2.0d); try { Map<String,ClassNode> variableTypes = new HashMap<String, ClassNode>(); variableTypes.put("score", ClassHelper.double_TYPE); Map<String,Object> options = new HashMap<String, Object>(); options.put(VAR_TYPES, variableTypes); COMPILE_OPTIONS.set(options); GroovyShell shell = new GroovyShell(binding, conf); Double userScore = (Double) shell.evaluate("abs(cos(1+score));System.exit(-1)"); System.out.println("userScore = " + userScore); } finally { COMPILE_OPTIONS.remove(); } } } 

  1. ThreadLocal ,
  2. — SecureExtension4.groovy
  3. variableTypes — “ → ”
  4. score
  5. options —
  6. "variable types" VAR_TYPES
  7. thread local
  8. , , thread local

:


 import static Sandbox.* def typesOfVariables = COMPILE_OPTIONS.get()[VAR_TYPES] unresolvedVariable { var -> if (typesOfVariables[var.name]) { return makeDynamic(var, typesOfVariables[var.name]) } } 

  1. thread local
  2. , ,
  3. type checker

thread local, , type checker . , unresolvedVariable , , , type checker, . , . !


. , .



. , . , , . , System.exit , :


 java.lang.System#exit(int) 

, Java, :


 public class Sandbox { public static final String WHITELIST_PATTERNS = "sandboxing.whitelist.patterns"; // ... public static void main(String[] args) { // ... try { Map<String,ClassNode> variableTypes = new HashMap<String, ClassNode>(); variableTypes.put("score", ClassHelper.double_TYPE); Map<String,Object> options = new HashMap<String, Object>(); List<String> patterns = new ArrayList<String>(); patterns.add("java\\.lang\\.Math#"); options.put(VAR_TYPES, variableTypes); options.put(WHITELIST_PATTERNS, patterns); COMPILE_OPTIONS.set(options); GroovyShell shell = new GroovyShell(binding, conf); Double userScore = (Double) shell.evaluate("abs(cos(1+score));System.exit(-1)"); System.out.println("userScore = " + userScore); } finally { COMPILE_OPTIONS.remove(); } } } 

  1. java.lang.Math

:


 import groovy.transform.CompileStatic import org.codehaus.groovy.ast.ClassNode import org.codehaus.groovy.ast.MethodNode import org.codehaus.groovy.ast.Parameter import org.codehaus.groovy.transform.stc.ExtensionMethodNode import static Sandbox.* @CompileStatic private static String prettyPrint(ClassNode node) { node.isArray()?"${prettyPrint(node.componentType)}[]":node.toString(false) } @CompileStatic private static String toMethodDescriptor(MethodNode node) { if (node instanceof ExtensionMethodNode) { return toMethodDescriptor(node.extensionMethodNode) } def sb = new StringBuilder() sb.append(node.declaringClass.toString(false)) sb.append("#") sb.append(node.name) sb.append('(') sb.append(node.parameters.collect { Parameter it -> prettyPrint(it.originType) }.join(',')) sb.append(')') sb } def typesOfVariables = COMPILE_OPTIONS.get()[VAR_TYPES] def whiteList = COMPILE_OPTIONS.get()[WHITELIST_PATTERNS] onMethodSelection { expr, MethodNode methodNode -> def descr = toMethodDescriptor(methodNode) if (!whiteList.any { descr =~ it }) { addStaticTypeError("You tried to call a method which is not allowed, what did you expect?: $descr", expr) } } unresolvedVariable { var -> if (typesOfVariables[var.name]) { return makeDynamic(var, typesOfVariables[var.name]) } } 

  1. MethodNode
  2. thread local
  3. ,

, :


 Script1.groovy: 1: [Static type checking] - You tried to call a method which is not allowed, what did you expect?: java.lang.System#exit(int) @ line 1, column 19. abs(cos(1+score));System.exit(-1) ^ 1 error 

, ! , , . , ! , , . , ( foo.text , foo.getText() ).


すべおをたずめる


, type checker' "property selection", , . , , . , , — . .


SandboxingTypeCheckingExtension.groovy


 import groovy.transform.CompileStatic import org.codehaus.groovy.ast.ClassCodeVisitorSupport import org.codehaus.groovy.ast.ClassHelper import org.codehaus.groovy.ast.ClassNode import org.codehaus.groovy.ast.MethodNode import org.codehaus.groovy.ast.Parameter import org.codehaus.groovy.ast.expr.PropertyExpression import org.codehaus.groovy.control.SourceUnit import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys import org.codehaus.groovy.transform.stc.ExtensionMethodNode import org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport import static Sandbox.* class SandboxingTypeCheckingExtension extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { @CompileStatic private static String prettyPrint(ClassNode node) { node.isArray()?"${prettyPrint(node.componentType)}[]":node.toString(false) } @CompileStatic private static String toMethodDescriptor(MethodNode node) { if (node instanceof ExtensionMethodNode) { return toMethodDescriptor(node.extensionMethodNode) } def sb = new StringBuilder() sb.append(node.declaringClass.toString(false)) sb.append("#") sb.append(node.name) sb.append('(') sb.append(node.parameters.collect { Parameter it -> prettyPrint(it.originType) }.join(',')) sb.append(')') sb } @Override Object run() { // Fetch white list of regular expressions of authorized method calls def whiteList = COMPILE_OPTIONS.get()[WHITELIST_PATTERNS] def typesOfVariables = COMPILE_OPTIONS.get()[VAR_TYPES] onMethodSelection { expr, MethodNode methodNode -> def descr = toMethodDescriptor(methodNode) if (!whiteList.any { descr =~ it }) { addStaticTypeError("You tried to call a method which is not allowed, what did you expect?: $descr", expr) } } unresolvedVariable { var -> if (isDynamic(var) && typesOfVariables[var.name]) { storeType(var, typesOfVariables[var.name]) handled = true } } // handling properties (like foo.text) is harder because the type checking extension // does not provide a specific hook for this. Harder, but not impossible! afterVisitMethod { methodNode -> def visitor = new PropertyExpressionChecker(context.source, whiteList) visitor.visitMethod(methodNode) } } private class PropertyExpressionChecker extends ClassCodeVisitorSupport { private final SourceUnit unit private final List<String> whiteList PropertyExpressionChecker(final SourceUnit unit, final List<String> whiteList) { this.unit = unit this.whiteList = whiteList } @Override protected SourceUnit getSourceUnit() { unit } @Override void visitPropertyExpression(final PropertyExpression expression) { super.visitPropertyExpression(expression) ClassNode owner = expression.objectExpression.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER) if (owner) { if (expression.spreadSafe && StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(owner, classNodeFor(Collection))) { owner = typeCheckingVisitor.inferComponentType(owner, ClassHelper.int_TYPE) } def descr = "${prettyPrint(owner)}#${expression.propertyAsString}" if (!whiteList.any { descr =~ it }) { addStaticTypeError("Property is not allowed: $descr", expression) } } } } }```     sandbox',     assert' ,  ,     : ``Sandbox.java`` ```java public class Sandbox { public static final String WHITELIST_PATTERNS = "sandboxing.whitelist.patterns"; public static final String VAR_TYPES = "sandboxing.variable.types"; public static final ThreadLocal<Map<String, Object>> COMPILE_OPTIONS = new ThreadLocal<Map<String, Object>>(); public static void main(String[] args) { CompilerConfiguration conf = new CompilerConfiguration(); ImportCustomizer customizer = new ImportCustomizer(); customizer.addStaticStars("java.lang.Math"); ASTTransformationCustomizer astcz = new ASTTransformationCustomizer( singletonMap("extensions", singletonList("SandboxingTypeCheckingExtension.groovy")), CompileStatic.class); conf.addCompilationCustomizers(astcz); conf.addCompilationCustomizers(customizer); Binding binding = new Binding(); binding.setVariable("score", 2.0d); try { Map<String, ClassNode> variableTypes = new HashMap<String, ClassNode>(); variableTypes.put("score", ClassHelper.double_TYPE); Map<String, Object> options = new HashMap<String, Object>(); List<String> patterns = new ArrayList<String>(); // allow method calls on Math patterns.add("java\\.lang\\.Math#"); // allow constructors calls on File patterns.add("File#<init>"); // because we let the user call each/times/... patterns.add("org\\.codehaus\\.groovy\\.runtime\\.DefaultGroovyMethods"); options.put(VAR_TYPES, variableTypes); options.put(WHITELIST_PATTERNS, patterns); COMPILE_OPTIONS.set(options); GroovyShell shell = new GroovyShell(binding, conf); Object result; try { result = shell.evaluate("Eval.me('1')"); // error assert false; } catch (MultipleCompilationErrorsException e) { System.out.println("Successful sandboxing: "+e.getMessage()); } try { result = shell.evaluate("System.exit(-1)"); // error assert false; } catch (MultipleCompilationErrorsException e) { System.out.println("Successful sandboxing: "+e.getMessage()); } try { result = shell.evaluate("((Object)Eval).me('1')"); // error assert false; } catch (MultipleCompilationErrorsException e) { System.out.println("Successful sandboxing: "+e.getMessage()); } try { result = shell.evaluate("new File('/etc/passwd').getText()"); // getText is not allowed assert false; } catch (MultipleCompilationErrorsException e) { System.out.println("Successful sandboxing: "+e.getMessage()); } try { result = shell.evaluate("new File('/etc/passwd').text"); // getText is not allowed assert false; } catch (MultipleCompilationErrorsException e) { System.out.println("Successful sandboxing: "+e.getMessage()); } Double userScore = (Double) shell.evaluate("abs(cos(1+score))"); System.out.println("userScore = " + userScore); } finally { COMPILE_OPTIONS.remove(); } } } 

おわりに


Groovy JVM. , . , , , . , Groovy, sandboxing' (, , ).


, , . , . , , .


, sandboxing', , — SecureASTCustomizer . , , : secure AST customizer , (, ), ( , ).


, : , , . Groovy . Groovy, , - pull request, - !



Source: https://habr.com/ru/post/J445114/


All Articles