亚欧色一区w666天堂,色情一区二区三区免费看,少妇特黄A片一区二区三区,亚洲人成网站999久久久综合,国产av熟女一区二区三区

  • 發布文章
  • 消息中心
點贊
收藏
評論
分享
原創

如何在Java中安全運行用戶的groovy代碼

2023-08-23 10:06:30
901
0

在Java中安全運行用戶的groovy代碼,會遇到很多挑戰

1,如何在Java中運行groovy代碼

pom.xml中加入依賴

<dependency>
    <groupId>org.apache.groovy</groupId>
    <artifactId>groovy</artifactId>
    <version>4.0.8</version>
</dependency>

Java代碼中常用的運行groovy方法有

// 執行代碼字符串
GroovyShell groovyShell = new GroovyShell();
groovyShell.evaluate("println 'Hello World!'");

// 執行groovy文件
GroovyShell groovyShell = new GroovyShell();
groovyShell.evaluate(new File("/path/to/groovy/test.groovy"));

// 先解析groovy文件,再運行其中的方法
GroovyShell groovyShell = new GroovyShell();
Script script = groovyShell.parse(new File("/path/to/groovy/test.groovy"));
Object[] args = {};
script.invokeMethod("run", args);

除了GroovyShell,groovy官方還提供了GroovyClassLoader和GroovyScriptEngine等其他執行groovy代碼的入口

2,會有哪些危險的代碼

因為groovy的強大,使得groovy可以非常方便地調用java的類和操作系統命令

// 1,直接退出Java進程
java.lang.System.exit(0);

// 2,執行Shell命令
java.lang.Runtime.getRuntime().exec("whoami");

// 3,調用java.io.File相關方法操作系統文件
def list = new java.io.File("/").list();
println list

// 4,直接調用shell命令
def cmd = "whoami"
println cmd.execute().text

// 5,使用AST來執行shell命令
@groovy.transform.ASTTest(value={
   assert java.lang.Runtime.getRuntime().exec("whoami")
})
def x

// 6,無限循環
while(true) {
  println "xxx"
}

// 7,長時間sleep
sleep 100000

// 8,申請大塊內存空間
def b = new byte[1024*1024*1024]
println b.length

由于本人使用groovy時間尚短,不確定以上代碼沒有枚舉完所有類型的危險代碼.如果有groovy大佬發現其他類型的危險代碼,歡迎在評論區留言

為了避免執行以上的危險代碼,必須給強大的groovy來一次能力閹割

3,groovy自定義安全配置

groovy官方提供了自定義的安全配置功能,來限制groovy的能力

3.1,SecureASTCustomizer

GroovyShell在執行groovy代碼前,會先將groovy代碼編譯成java class,然后在JVM中執行.

而groovy官方提供的SecureASTCustomizer可以在編譯階段對危險代碼進行一次過濾,利用SecureASTCustomizer,我們可以

  • 指定import的黑名單或白名單(二者只能選其一),支持星號(如 groovy.json.*)
  • 語法關鍵詞的黑名單或白名單(二者只能選其一)
  • 其他語法開關,如是否允許閉包,是否允許定義方法等

因為很難枚舉出所有有危險的java類,用戶可以利用依賴引用來繞過import黑名單,所以使用import黑名單是不夠安全的,例如我們禁止用戶import java.io.File,但用戶還是可以import其他類來間接操作系統文件

所以,利用SecureASTCustomizer的import白名單功能,并細心甄別可開放的java包,我們基本上可以避免執行第3種危險代碼

private GroovyShell getShell() {
    final SecureASTCustomizer secure = new SecureASTCustomizer();// 創建SecureASTCustomizer
		secure.setClosuresAllowed(true);// 允許使用閉包
		List<Integer> tokensBlacklist = new ArrayList<>();
		tokensBlacklist.add(Types.KEYWORD_WHILE);// 添加關鍵字黑名單 while和goto
		tokensBlacklist.add(Types.KEYWORD_GOTO);
		secure.setDisallowedTokens(tokensBlacklist);
		secure.setIndirectImportCheckEnabled(false);// 設置為false, 可以在代碼中定義并直接使用class, 否則需要在白名單中指定
		secure.setAllowedStarImports(Arrays.asList("org.codehaus.groovy.runtime.*", "groovy.json.*"));

		final CompilerConfiguration config = new CompilerConfiguration();// 自定義CompilerConfiguration,設置AST
		
    config.addCompilationCustomizers(secure);
		GroovyShell shell = new GroovyShell(config);
    return shell;
}

注意,使用了import白名單后,需要設置setIndirectImportCheckEnabled(false),否則在groovy中定義的class也要加入到白名單中才能使用

由于groovy會自動引入java.util,java.lang包,所以import白名單并不能阻止用戶調用System.exit()方法

3.2,GroovyInterceptor

jenkins中使用了GroovyInterceptor攔截器來限制groovy的能力,我們也可以將groovy沙箱引入自己的項目中

<dependency>
   <groupId>org.craftercms</groupId>
   <artifactId>groovy-sandbox</artifactId>
    <version>4.0.2</version>
</dependency>

GroovyInterceptor攔截器作用于運行階段,為我們提供了多個介入代碼運行的時機

static class GroovyNotSupportInterceptor extends GroovyInterceptor {

		private final List<String> defaultMethodBlacklist = Arrays.asList("getClass", "class", "wait", "notify",
				"notifyAll", "invokeMethod", "finalize", "execute");
		
		/**
		 * 靜態方法攔截
		 */
		@Override
		public Object onStaticCall(GroovyInterceptor.Invoker invoker, @SuppressWarnings("rawtypes") Class receiver,
				String method, Object... args) throws Throwable {
			System.out.println("onStaticCall: " + method);
			if (receiver == System.class && "exit".equals(method)) {
				// System.exit(0)
				throw new SecurityException("Don't call on System.exit() please");
			} else if (receiver == Runtime.class) {
				// 通過Java的Runtime.getRuntime().exec()方法執行shell, 操作服務器…
				throw new SecurityException("Don't call on RunTime please");
			} else if (receiver == Class.class && "forName".equals(method)) {
				// Class.forName
				throw new SecurityException("Don't call on Class.forName please");
			}
			return super.onStaticCall(invoker, receiver, method, args);
		}

		/**
		 * 普通方法攔截
		 */
		@Override
		public Object onMethodCall(GroovyInterceptor.Invoker invoker, Object receiver, String method, Object... args)
				throws Throwable {
			System.out.println("onMethodCall: " + method);
			if (defaultMethodBlacklist.contains(method)) {
				// 方法列表黑名單
				throw new SecurityException("Not support method: " + method);
			}
			return super.onMethodCall(invoker, receiver, method, args);
		}
}
// 執行前在當前線程注冊方法攔截
new GroovyNotSupportInterceptor().register();
GroovyShell shell = getShell();
shell.evaluate(script)

通過GroovyInterceptor攔截器和SecureASTCustomizer,我們基本上可以避免執行前4種危險代碼了

3.3,禁用AST轉換

groovy的AST轉換發生在編譯階段之前,所以以上方法都無法阻止,目前沒找到比較好的方法

只能使用最笨的方法,執行代碼前用正則搜索全文,只要在引號或雙引號外面發現符號,則拒絕執行

3.4,自定義超時控制

思路是利用GroovyInterceptor,在每個攔截點判斷當前是否超時,超時則中斷執行

至于代碼中的 sleep 10000000,思路也是利用GroovyInterceptor攔截sleep方法,當sleep方法入參大于5000毫秒時,將入參重置為5000

至此,前7種危險代碼基本上能有效避免了

4,groovy線程的內存控制

控制groovy代碼占用的內存,最有效的方案是將groovy代碼放到容器中執行

其他方案不能準確控制線程的內存

delight的narshon沙箱使用了ThreadMXBean.getThreadAllocatedBytes(線程ID)方法來監控線程已分配的內存大小,當這個值超過預設值時,則會中斷執行

但這個方法是返回指定線程自啟動以來在堆上分配的內存總量,這個值只會增大不會減小(即使線程創建的對象已經被回收).實際上也沒有方法能獲取指定線程占用堆的內存總量,因為堆內存是線程共享的

 

總結,在Java中運行用戶的groovy代碼,最完美的方案就是放到容器中執行,也就是Faas方案,這樣的話不需要限制groovy的能力,也可以限制其占用的CPU和內存資源.

如果采用非Faas方案,本文基于groovy官方的SecureASTCustomizer和jenkins的GroovyInterceptor給出了能有效避免大部分危險代碼的方案,如果您有其他建議,歡迎評論區留言.

 

0條評論
0 / 1000
白龍馬
14文章數
0粉絲數
白龍馬
14 文章 | 0 粉絲
原創

如何在Java中安全運行用戶的groovy代碼

2023-08-23 10:06:30
901
0

在Java中安全運行用戶的groovy代碼,會遇到很多挑戰

1,如何在Java中運行groovy代碼

pom.xml中加入依賴

<dependency>
    <groupId>org.apache.groovy</groupId>
    <artifactId>groovy</artifactId>
    <version>4.0.8</version>
</dependency>

Java代碼中常用的運行groovy方法有

// 執行代碼字符串
GroovyShell groovyShell = new GroovyShell();
groovyShell.evaluate("println 'Hello World!'");

// 執行groovy文件
GroovyShell groovyShell = new GroovyShell();
groovyShell.evaluate(new File("/path/to/groovy/test.groovy"));

// 先解析groovy文件,再運行其中的方法
GroovyShell groovyShell = new GroovyShell();
Script script = groovyShell.parse(new File("/path/to/groovy/test.groovy"));
Object[] args = {};
script.invokeMethod("run", args);

除了GroovyShell,groovy官方還提供了GroovyClassLoader和GroovyScriptEngine等其他執行groovy代碼的入口

2,會有哪些危險的代碼

因為groovy的強大,使得groovy可以非常方便地調用java的類和操作系統命令

// 1,直接退出Java進程
java.lang.System.exit(0);

// 2,執行Shell命令
java.lang.Runtime.getRuntime().exec("whoami");

// 3,調用java.io.File相關方法操作系統文件
def list = new java.io.File("/").list();
println list

// 4,直接調用shell命令
def cmd = "whoami"
println cmd.execute().text

// 5,使用AST來執行shell命令
@groovy.transform.ASTTest(value={
   assert java.lang.Runtime.getRuntime().exec("whoami")
})
def x

// 6,無限循環
while(true) {
  println "xxx"
}

// 7,長時間sleep
sleep 100000

// 8,申請大塊內存空間
def b = new byte[1024*1024*1024]
println b.length

由于本人使用groovy時間尚短,不確定以上代碼沒有枚舉完所有類型的危險代碼.如果有groovy大佬發現其他類型的危險代碼,歡迎在評論區留言

為了避免執行以上的危險代碼,必須給強大的groovy來一次能力閹割

3,groovy自定義安全配置

groovy官方提供了自定義的安全配置功能,來限制groovy的能力

3.1,SecureASTCustomizer

GroovyShell在執行groovy代碼前,會先將groovy代碼編譯成java class,然后在JVM中執行.

而groovy官方提供的SecureASTCustomizer可以在編譯階段對危險代碼進行一次過濾,利用SecureASTCustomizer,我們可以

  • 指定import的黑名單或白名單(二者只能選其一),支持星號(如 groovy.json.*)
  • 語法關鍵詞的黑名單或白名單(二者只能選其一)
  • 其他語法開關,如是否允許閉包,是否允許定義方法等

因為很難枚舉出所有有危險的java類,用戶可以利用依賴引用來繞過import黑名單,所以使用import黑名單是不夠安全的,例如我們禁止用戶import java.io.File,但用戶還是可以import其他類來間接操作系統文件

所以,利用SecureASTCustomizer的import白名單功能,并細心甄別可開放的java包,我們基本上可以避免執行第3種危險代碼

private GroovyShell getShell() {
    final SecureASTCustomizer secure = new SecureASTCustomizer();// 創建SecureASTCustomizer
		secure.setClosuresAllowed(true);// 允許使用閉包
		List<Integer> tokensBlacklist = new ArrayList<>();
		tokensBlacklist.add(Types.KEYWORD_WHILE);// 添加關鍵字黑名單 while和goto
		tokensBlacklist.add(Types.KEYWORD_GOTO);
		secure.setDisallowedTokens(tokensBlacklist);
		secure.setIndirectImportCheckEnabled(false);// 設置為false, 可以在代碼中定義并直接使用class, 否則需要在白名單中指定
		secure.setAllowedStarImports(Arrays.asList("org.codehaus.groovy.runtime.*", "groovy.json.*"));

		final CompilerConfiguration config = new CompilerConfiguration();// 自定義CompilerConfiguration,設置AST
		
    config.addCompilationCustomizers(secure);
		GroovyShell shell = new GroovyShell(config);
    return shell;
}

注意,使用了import白名單后,需要設置setIndirectImportCheckEnabled(false),否則在groovy中定義的class也要加入到白名單中才能使用

由于groovy會自動引入java.util,java.lang包,所以import白名單并不能阻止用戶調用System.exit()方法

3.2,GroovyInterceptor

jenkins中使用了GroovyInterceptor攔截器來限制groovy的能力,我們也可以將groovy沙箱引入自己的項目中

<dependency>
   <groupId>org.craftercms</groupId>
   <artifactId>groovy-sandbox</artifactId>
    <version>4.0.2</version>
</dependency>

GroovyInterceptor攔截器作用于運行階段,為我們提供了多個介入代碼運行的時機

static class GroovyNotSupportInterceptor extends GroovyInterceptor {

		private final List<String> defaultMethodBlacklist = Arrays.asList("getClass", "class", "wait", "notify",
				"notifyAll", "invokeMethod", "finalize", "execute");
		
		/**
		 * 靜態方法攔截
		 */
		@Override
		public Object onStaticCall(GroovyInterceptor.Invoker invoker, @SuppressWarnings("rawtypes") Class receiver,
				String method, Object... args) throws Throwable {
			System.out.println("onStaticCall: " + method);
			if (receiver == System.class && "exit".equals(method)) {
				// System.exit(0)
				throw new SecurityException("Don't call on System.exit() please");
			} else if (receiver == Runtime.class) {
				// 通過Java的Runtime.getRuntime().exec()方法執行shell, 操作服務器…
				throw new SecurityException("Don't call on RunTime please");
			} else if (receiver == Class.class && "forName".equals(method)) {
				// Class.forName
				throw new SecurityException("Don't call on Class.forName please");
			}
			return super.onStaticCall(invoker, receiver, method, args);
		}

		/**
		 * 普通方法攔截
		 */
		@Override
		public Object onMethodCall(GroovyInterceptor.Invoker invoker, Object receiver, String method, Object... args)
				throws Throwable {
			System.out.println("onMethodCall: " + method);
			if (defaultMethodBlacklist.contains(method)) {
				// 方法列表黑名單
				throw new SecurityException("Not support method: " + method);
			}
			return super.onMethodCall(invoker, receiver, method, args);
		}
}
// 執行前在當前線程注冊方法攔截
new GroovyNotSupportInterceptor().register();
GroovyShell shell = getShell();
shell.evaluate(script)

通過GroovyInterceptor攔截器和SecureASTCustomizer,我們基本上可以避免執行前4種危險代碼了

3.3,禁用AST轉換

groovy的AST轉換發生在編譯階段之前,所以以上方法都無法阻止,目前沒找到比較好的方法

只能使用最笨的方法,執行代碼前用正則搜索全文,只要在引號或雙引號外面發現符號,則拒絕執行

3.4,自定義超時控制

思路是利用GroovyInterceptor,在每個攔截點判斷當前是否超時,超時則中斷執行

至于代碼中的 sleep 10000000,思路也是利用GroovyInterceptor攔截sleep方法,當sleep方法入參大于5000毫秒時,將入參重置為5000

至此,前7種危險代碼基本上能有效避免了

4,groovy線程的內存控制

控制groovy代碼占用的內存,最有效的方案是將groovy代碼放到容器中執行

其他方案不能準確控制線程的內存

delight的narshon沙箱使用了ThreadMXBean.getThreadAllocatedBytes(線程ID)方法來監控線程已分配的內存大小,當這個值超過預設值時,則會中斷執行

但這個方法是返回指定線程自啟動以來在堆上分配的內存總量,這個值只會增大不會減小(即使線程創建的對象已經被回收).實際上也沒有方法能獲取指定線程占用堆的內存總量,因為堆內存是線程共享的

 

總結,在Java中運行用戶的groovy代碼,最完美的方案就是放到容器中執行,也就是Faas方案,這樣的話不需要限制groovy的能力,也可以限制其占用的CPU和內存資源.

如果采用非Faas方案,本文基于groovy官方的SecureASTCustomizer和jenkins的GroovyInterceptor給出了能有效避免大部分危險代碼的方案,如果您有其他建議,歡迎評論區留言.

 

文章來自個人專欄
文章 | 訂閱
0條評論
0 / 1000
請輸入你的評論
0
0