From 3d7f0a2bd4b3cdb985cc8e52654d0957bef88811 Mon Sep 17 00:00:00 2001 From: JoyChou Date: Thu, 14 Sep 2017 17:38:34 +0800 Subject: [PATCH] add getRealIP --- .../Maven__javax_servlet_servlet_api_2_5.xml | 13 + ...__org_apache_commons_commons_lang3_3_0.xml | 13 + ..._tomcat_embed_tomcat_embed_core_8_0_23.xml | 13 + ...he_tomcat_embed_tomcat_embed_el_8_0_23.xml | 13 + ...embed_tomcat_embed_logging_juli_8_0_23.xml | 13 + ...at_embed_tomcat_embed_websocket_8_0_23.xml | 13 + ...ring_boot_starter_tomcat_1_2_4_RELEASE.xml | 13 + .idea/workspace.xml | 295 ++++++++++++++---- README.md | 26 +- pom.xml | 22 ++ src/main/java/IPAddress.java | 29 ++ src/main/java/{security.java => SSRF.java} | 134 +++----- src/main/java/checkURL.java | 51 +++ src/main/java/test.java | 7 +- target/classes/IPAddress.class | Bin 0 -> 2323 bytes target/classes/ssrf.class | Bin 0 -> 2979 bytes target/classes/test.class | Bin 0 -> 1892 bytes target/classes/url.class | Bin 0 -> 1457 bytes trident.iml | 7 + 19 files changed, 505 insertions(+), 157 deletions(-) create mode 100644 .idea/libraries/Maven__javax_servlet_servlet_api_2_5.xml create mode 100644 .idea/libraries/Maven__org_apache_commons_commons_lang3_3_0.xml create mode 100644 .idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_core_8_0_23.xml create mode 100644 .idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_el_8_0_23.xml create mode 100644 .idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_logging_juli_8_0_23.xml create mode 100644 .idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_websocket_8_0_23.xml create mode 100644 .idea/libraries/Maven__org_springframework_boot_spring_boot_starter_tomcat_1_2_4_RELEASE.xml create mode 100644 src/main/java/IPAddress.java rename src/main/java/{security.java => SSRF.java} (62%) create mode 100644 src/main/java/checkURL.java create mode 100644 target/classes/IPAddress.class create mode 100644 target/classes/ssrf.class create mode 100644 target/classes/test.class create mode 100644 target/classes/url.class diff --git a/.idea/libraries/Maven__javax_servlet_servlet_api_2_5.xml b/.idea/libraries/Maven__javax_servlet_servlet_api_2_5.xml new file mode 100644 index 0000000..679e09a --- /dev/null +++ b/.idea/libraries/Maven__javax_servlet_servlet_api_2_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_commons_commons_lang3_3_0.xml b/.idea/libraries/Maven__org_apache_commons_commons_lang3_3_0.xml new file mode 100644 index 0000000..de0cf71 --- /dev/null +++ b/.idea/libraries/Maven__org_apache_commons_commons_lang3_3_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_core_8_0_23.xml b/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_core_8_0_23.xml new file mode 100644 index 0000000..7e2d2eb --- /dev/null +++ b/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_core_8_0_23.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_el_8_0_23.xml b/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_el_8_0_23.xml new file mode 100644 index 0000000..29f4076 --- /dev/null +++ b/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_el_8_0_23.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_logging_juli_8_0_23.xml b/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_logging_juli_8_0_23.xml new file mode 100644 index 0000000..8683ea2 --- /dev/null +++ b/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_logging_juli_8_0_23.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_websocket_8_0_23.xml b/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_websocket_8_0_23.xml new file mode 100644 index 0000000..0250d9a --- /dev/null +++ b/.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_websocket_8_0_23.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_tomcat_1_2_4_RELEASE.xml b/.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_tomcat_1_2_4_RELEASE.xml new file mode 100644 index 0000000..a28ccd7 --- /dev/null +++ b/.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_tomcat_1_2_4_RELEASE.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 3bb339b..288c081 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,8 +2,12 @@ + + + + @@ -32,19 +36,19 @@ - - + + - + - - + + @@ -52,15 +56,32 @@ - - + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -68,13 +89,25 @@ - - + + + + + + + + + + + + + + @@ -88,6 +121,7 @@ print dns + split @@ -102,9 +136,16 @@ @@ -116,7 +157,8 @@ DEFINITION_ORDER - @@ -136,6 +178,9 @@ + + + @@ -154,6 +199,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -653,12 +783,12 @@ - + - @@ -670,19 +800,20 @@ - + + - + @@ -694,12 +825,11 @@ - + - @@ -742,8 +872,14 @@ - - + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index e2a9403..22e87a1 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,12 @@ 合法URL返回true,非法URL返回false。 ```java -security urlCheck = new security(); +// URL白名单组件测试 +checkURL urlCheck = new checkURL(); String[] urlWList = {"joychou.com", "joychou.me"}; -Boolean ret = urlCheck.checkUrlWlist("http://test.joychou.me", urlWList); +Boolean ret = urlCheck.checkUrlWlist("http://test.joychou.org", urlWList); +System.out.println(ret); + ``` ## checkSSRF @@ -45,11 +48,13 @@ Boolean ret = urlCheck.checkUrlWlist("http://test.joychou.me", urlWList); URL只支持HTTP协议。 ```java -security urlCheck = new security(); +// SSRF组件测试 +SSRF check = new SSRF(); String url = "http://dns_rebind.joychou.me"; -ret = urlCheck.checkSSRF(url); +ret = check.checkSSRF(url); if (ret){ - // curl url + String con = Request.Get(url).execute().returnContent().toString(); + System.out.println(con); } else { System.out.println("Bad boy. The url is illegal"); @@ -59,9 +64,12 @@ else { ### 绕过姿势 -以上代码在设置TTL为0的情况,可以用以下方法绕过 : +以上代码在设置TTL为0的情况,可以用DNS Rebinding绕过。 -1. DNS Rebind -2. 域名解析2个A记录地址(分别为外网和内网) +但是,只要Java不设置TTL为0,该代码逻辑上不存在被绕过风险。 + +## 获取真实IP + + +用这份代码,必须保证,前面Proxy有把真实IP放到X-Real-IP头。 -也就是说,只要Java不设置TTL为0,该代码逻辑上不存在被绕过风险。 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 37b2d20..6fe8c78 100644 --- a/pom.xml +++ b/pom.xml @@ -21,5 +21,27 @@ 4.3.6 + + + javax.servlet + servlet-api + 2.5 + provided + + + + + org.springframework.boot + spring-boot-starter-tomcat + 1.2.4.RELEASE + + + + + org.apache.commons + commons-lang3 + 3.0 + + \ No newline at end of file diff --git a/src/main/java/IPAddress.java b/src/main/java/IPAddress.java new file mode 100644 index 0000000..a4a484a --- /dev/null +++ b/src/main/java/IPAddress.java @@ -0,0 +1,29 @@ +import org.apache.commons.lang3.StringUtils; +import javax.servlet.http.HttpServletRequest; + +/** + * date: 17/9/14. + * author: JoyChou(https://joychou.org) + */ + +public class IPAddress { + /** + * 从Header里的X-Real-IP获取IP地址,如果为空,取Remote_Addr + * @param request + * @return + */ + public static String getIPFromRealIPHeader(HttpServletRequest request){ + String ip = request.getHeader("X-Real-IP"); + if (StringUtils.isNotBlank("ip")) { + return ip; + }else { + String remoteAddr = request.getRemoteAddr(); + if (StringUtils.isNotBlank(remoteAddr)) { + return remoteAddr; + } + } + return ""; + } + + +} diff --git a/src/main/java/security.java b/src/main/java/SSRF.java similarity index 62% rename from src/main/java/security.java rename to src/main/java/SSRF.java index e23c999..fd092eb 100644 --- a/src/main/java/security.java +++ b/src/main/java/SSRF.java @@ -1,72 +1,20 @@ +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.URL; + /** * Author: JoyChou * Mail: viarus#qq.com * Date: 2017.09.05 */ -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.URL; -import com.google.common.net.InternetDomainName; - -public class security { - - /* - * 检测传入的URL是否在白名单的域名里 - * url:需要检测的URL - * urlWList: 一级域名的域名列表,比如String[] urlWList = {"joychou.com", "joychou.me"}; - * 返回值:合法URL返回true,非法URL返回false - */ - public static Boolean checkUrlWlist(String url, String[] urlWList) { - try { - URL u = new URL(url); - // 只允许http和https的协议 - if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) { - return false; - } - // 获取域名,并转为小写 - String host = u.getHost().toLowerCase(); - // 获取一级域名 - String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString(); - - for (String whiteUrl: urlWList){ - if (rootDomain.equals(whiteUrl)) { - return true; - } - } - return false; - - } catch (Exception e) { - return false; - } - } - - - /* - * 判断一个URL的IP是否是内网IP - * 如果是内网IP,返回true - * 非内网IP,返回false - */ - public static boolean isInnerIpFromUrl(String url) throws Exception { - String domain = getUrlDomain(url); - if (domain.equals("")) { - return true; // 异常URL当成内网IP等非法URL处理 - } - - String ip = DomainToIP(domain); - if(ip.equals("")){ - return true; // 如果域名转换为IP异常,则认为是非法URL - } - return isInnerIp(ip); - } - - - /* - * check SSRF (判断逻辑为判断URL的IP是否是内网IP) - * 如果是内网IP,返回false,表示checkSSRF不通过。否则返回true。即合法返回true - * URL只支持HTTP协议 - * 设置了访问超时时间为3s +public class SSRF { + /** + * check SSRF (判断逻辑为判断URL的IP是否是内网IP) + * 如果是内网IP,返回false,表示checkSSRF不通过。否则返回true。即合法返回true + * URL只支持HTTP协议 + * 设置了访问超时时间为3s */ public static Boolean checkSSRF(String url) { @@ -103,13 +51,34 @@ public class security { return true; } - /* - 内网IP: - 10.0.0.1 - 10.255.255.254 (10.0.0.0/8) - 192.168.0.1 - 192.168.255.254 (192.168.0.0/16) - 127.0.0.1 - 127.255.255.254 (127.0.0.0/8) - 172.16.0.1 - 172.31.255.254 (172.16.0.0/12) - */ + + + /** + * 判断一个URL的IP是否是内网IP + * 如果是内网IP,返回true + * 非内网IP,返回false + */ + public static boolean isInnerIpFromUrl(String url) throws Exception { + String domain = getUrlDomain(url); + if (domain.equals("")) { + return true; // 异常URL当成内网IP等非法URL处理 + } + + String ip = DomainToIP(domain); + if(ip.equals("")){ + return true; // 如果域名转换为IP异常,则认为是非法URL + } + return isInnerIp(ip); + } + + + /** + * 内网IP: + * 10.0.0.1 - 10.255.255.254 (10.0.0.0/8) + * 192.168.0.1 - 192.168.255.254 (192.168.0.0/16) + * 127.0.0.1 - 127.255.255.254 (127.0.0.0/8) + * 172.16.0.1 - 172.31.255.254 (172.16.0.0/12) + */ public static boolean isInnerIp(String strIP) throws IOException { try{ String[] ipArr = strIP.split("\\."); @@ -128,12 +97,13 @@ public class security { } } - /* - * 域名转换为IP - * 会将各种进制的ip转为正常ip - * 167772161转换为10.0.0.1 - * 127.0.0.1.xip.io转换为127.0.0.1 - */ + + /** + * 域名转换为IP + * 会将各种进制的ip转为正常ip + * 167772161转换为10.0.0.1 + * 127.0.0.1.xip.io转换为127.0.0.1 + */ public static String DomainToIP(String domain) throws IOException{ try { InetAddress IpAddress = InetAddress.getByName(domain); // send dns request @@ -144,10 +114,10 @@ public class security { } } - /* - 从URL中获取域名 - 限制为http/https协议 - */ + /** + * 从URL中获取域名 + * 限制为http/https协议 + */ public static String getUrlDomain(String url) throws IOException{ try { URL u = new URL(url); @@ -160,8 +130,4 @@ public class security { } } - - public static void main(String[] args) throws Exception { - - } } diff --git a/src/main/java/checkURL.java b/src/main/java/checkURL.java new file mode 100644 index 0000000..a43c560 --- /dev/null +++ b/src/main/java/checkURL.java @@ -0,0 +1,51 @@ +/** + * Author: JoyChou + * Mail: viarus#qq.com + * Date: 2017.09.05 + */ + + +import java.net.URL; +import com.google.common.net.InternetDomainName; + + +public class checkURL { + + /** + * 检测传入的URL是否在白名单的域名里 + * url:需要检测的URL + * urlWList: 一级域名的域名列表,比如String[] urlWList = {"joychou.com", "joychou.me"}; + * 返回值:合法URL返回true,非法URL返回false + */ + public static Boolean checkUrlWlist(String url, String[] urlWList) { + try { + URL u = new URL(url); + // 只允许http和https的协议 + if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) { + return false; + } + // 获取域名,并转为小写 + String host = u.getHost().toLowerCase(); + // 获取一级域名 + String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString(); + + for (String whiteUrl: urlWList){ + if (rootDomain.equals(whiteUrl)) { + return true; + } + } + return false; + + } catch (Exception e) { + return false; + } + } + + + + + + public static void main(String[] args) throws Exception { + + } +} diff --git a/src/main/java/test.java b/src/main/java/test.java index 19cabc8..e5159ce 100644 --- a/src/main/java/test.java +++ b/src/main/java/test.java @@ -9,14 +9,15 @@ public class test { public static void main(String[] args) throws Exception { // URL白名单组件测试 - security urlCheck = new security(); + checkURL urlCheck = new checkURL(); String[] urlWList = {"joychou.com", "joychou.me"}; Boolean ret = urlCheck.checkUrlWlist("http://test.joychou.org", urlWList); System.out.println(ret); // SSRF组件测试 + SSRF check = new SSRF(); String url = "http://dns_rebind.joychou.me"; - ret = urlCheck.checkSSRF(url); + ret = check.checkSSRF(url); if (ret){ String con = Request.Get(url).execute().returnContent().toString(); System.out.println(con); @@ -24,5 +25,7 @@ public class test { else { System.out.println("Bad boy. The url is illegal"); } + + } } diff --git a/target/classes/IPAddress.class b/target/classes/IPAddress.class new file mode 100644 index 0000000000000000000000000000000000000000..3ef8d64afbf1474671694b9204a4c95e97497e57 GIT binary patch literal 2323 zcma)7O;;0V6n-X|$xPyaArNY`ehY>qkcw@pM5&0PsR1L1B3j!a8N%3nb!N19PEYH? zl{=So)#JvE3%aq`1>JSgKhPi0|Izk&XJVkooKso6x$k}NeeS)_y?6NI?_ak8oPrg> zASNS-<1=ww&@ijifP|8R%+D5ip{c7yJgsxxVM$E zyJk5O_7n@HsY=B*9Vag#Ts7T!)2J0n66TV3iw}%PMz&@&s@VnCwi?yEr&-TBrv0d9 zy4f|?ZDp_WSnw6|<~I*b$IXwt>nTBR+0FIGx7RG!EVLx_cY&9n+CGGYlkXXez|6W( znz5VprI{H*cyUAtv2JNdmISFFL0ey4W$m^tL7|_pk9UB460Vv?#pHWF`JTCStvh0y z^`>hImL&u%mL9g8LSvq^vN~~rIk7i_<{ViJpA!xPOw~*EH0cH1bi!gHCaK5Hm z*a&u2U!Y~gmwfB6)tYYBTZT;+&1x+)AKGP#iq*R0xU}^ZGnN>e-p*u#*h zes`knJs;tMaBf>QqijyqY7)+LF?x5qx`vOkZlmTDs*R>?P8$wKt82(*9Oc(-kl#%p z#KGXW@l5cR*M)EatzB{ulkid@HA8@*5}t z%x1O_+Q5xWOnnV)E|`Mrp;37LLZ=yB95z1%6Y+$nRJSi_7N7F*vg_&eSORl})R#4ETM z+v8QNq&HJ0{v@M#l13>cq$iW4jVC>L4!EDN4v8P`c~*p2~D+EvXUQ90@Ku5bWXiB;uJJn*vhcV&=`9Tv%Qck*7g K5@QQC&i@NI@ba$! literal 0 HcmV?d00001 diff --git a/target/classes/ssrf.class b/target/classes/ssrf.class new file mode 100644 index 0000000000000000000000000000000000000000..aadcd8b8c7493a19b9059777c694513e0edeff1e GIT binary patch literal 2979 zcmZ`*`&$%c6nXli{`l+XUjX!CHjZxW zk7F&KjAI-JWH_keP#jS_CBtDAN8*TKLIz!iNf|OSWMeR7J}SeMifOT9#&9f-r;(G* zj0{!`wn*}E9ETG_AqF>wVhkr@cqWdMI3>fg;_Y+{&xz`p7@k-0f{GX8SdINLyd?8k z8D5TKl@IL|73Wl(S5Wh?X_@XL3c~FjV+tbM?X00-aoV(uq2kP>;q28XbCe{~c1F*Q z>5eJ;Kp1hS%>r$t3kBzBzk=G#w2^swbaZ4u!J78;G5v(zlhduKo>A8^t*QQwD$zFE z&KbJJTZps?I7FG{-39ZL47T=#`X$c*eFY+B9uT zL92huGTfd)*Uj%6NpCNo5`e<^nyL)%Jee`_a+7D_qCkorH7z~op=&S;DOz??`2okC z@kC7vg?fOzWbGNl1EJ6MGwU`#agM+*6z>Go2y;5w;2 zG_}Jsyw{e^>V>>7XepK7n$40b`fsielcnK2!0zhO793l3tn#_x5d&aYLVs8@+M;Ln^Lm_!3`f_!{46_!i%=Ag5VL8m^16 zsI1AV55ly2Qp05s3ht~x^Rv3GXy&p8Gv2Vrv0Xc3=h_U%v7JqA8otB#8h#KjKYCt- zT`E>g3{M_oSu1F)h=N8?pq|ikMPry3n%fU7+-%yU-*;o8$r#%^Sf~pYFkgduJ8xK( zR$;>01FJzYD`zZ=mV)7?tb(gstPMMtv!59W8k^Ka2#6Ln>g;2m>U=3EZR9qvvDch2 z>>>k;w5LQ<&G;?K$A~34@~;LbYPNVi<%5P!CI?I-mt|dM%|iKB6=go_>__8xQP0us zzvA@??H3-RUZdUS?OL!qtFn-?Tw_YI8q4cW!Ql2kj)ImJ=y!O@6B4VskG;f*ww)T% zXPBAJ+qd<9?vZ+r3cE1h$8<2CsOsleK^+~;*Btp`t{k_p-*l%*j-F?-$>Q4EJFYSo z)G&R=Tzj5iRWd>tSiw(MH$Rd<4HS76IQDYa!%(q8Vqs*^g_CQcxB`3*xw&=dIi==SR2=og}FT zD;eK?*hI+|exE0>2GeMVg${o1JK>VV=c#p;ES)2X=SkuPvLrl>@c)Ksvh@%)^5Y-J zFYU>jIhj~e!X1|pjm#$J(6qa&vS%)uXQ}9iyo|3cXgEs8^>o@m(@iwHjE38=%Y)wL z8xDX(DIcJFb_=z4c|ZnfBSJ|dA@R8mApIFYn(R#6S;AfE%>5DmCis(7butzePvS8=&X}4R zQJM(~@igS6MWN0lc6(`=k9-v)=huoFl8g!7^AIr^%hRmDwj1fhk{gsti^Pf2N0*={ zV212ll;(-`c$x7irF-xM@6?fhDF?Y-?{N>}_8Awy{Avb%zob3!J1EV?o~;qzD-tV9 zxc3Uydh7eV^@>|qHGT`L$8TayqP>I;Ipb6UZzX@_uB(E0JL_Q{af{c|r|I=MHpCfL j%?mtzkvV^ z|3iO4Ki3b)Dy#hg{ZTIW&4WZ`EzDXo_ug~Q-uIk+&fNU}_MiU%Okp>U&oHYX8%K{E zb1LRk+*WZ%8t%sM4Ze-z9_AG+#LmjN>~zlCSUM_yPGi9%D&*FRNHl z@M9dK60oYGAg5~*^F+bAf(?P_ZOgHOy8@xq#JWIu!7Z5r19{6am+IA`>8SCRDUE9Hm2RJQYabilsg44zm<6vz+2?b|2JlG~lt3|G znMOU}*^{Y>{7ERk#;?I7?DD61vcUWn>z=dVIss!Bm^ks2mMafAL5CwwHE^543k;LK zBXg@99~81(B;G$UtG3`)Yr4n$k1OQIT(UmOuB_HFzsS+lR`+@BR>Vb|R)s2L5?Evc1Ys9YaBYucpx@oy^qAwR24{wY|4$!~Eoq<1bUM>dr zacUoleGKKI#EeJxG5i;LIZcdkdWcct$8yo}=tkmnV*CJS4sf>3-idx5gN?XV^kEGB zjCBB4kiZQLVFtsvgAv@9|1;cuhSR)#&hT!!fEqo2qg1~$SDE=LRQjmIrl<(>!~`Ze zs`R>wYt&_o*VlF0q#oNyQ_c`iS?4oFZDz=jB{o9lQA~3k#aG0KiM+|@YtDOc>m8W2 B+13C6 literal 0 HcmV?d00001 diff --git a/target/classes/url.class b/target/classes/url.class new file mode 100644 index 0000000000000000000000000000000000000000..d720be844c633cfdb6b07d771e7e0619c8052c05 GIT binary patch literal 1457 zcmb7ET~iZD6g^EQnIVjVK+vqZeq#_ME-I@*MMPa(hYv-i#PVSh+hp9yG)_9WKVbia zKKkOTzC>%S@(1`U{3Yt?2}&&3DtD@;`}XO+=iYm6&wTv+?*{Z2yR{pvhhbv*w&;q4` z_rzcKi=iJ?i_1C=qUzLxtV6RMAhXYPFkJh)BG+`FOpp{4XDE&_Hu;gZ%`{ZlW;apIkE_%n z`<>b>mQ^#Z$m@Yo&qNqEMY!n4fPkA6w2`;Lrd?ddj443tH5*r4%;GAQHOq#LYc8(i z4;OQoad87PHvV*R6P}BCEVx)S@Dgqb3~Va(JsE+%O?0vRL{=zKZkyhiQhKUVFU3l$ zilQ=uh0f+|wD1SMHpX%r+K+W(CD671AYT>OwVB68qXq1BKWxgy#{xrz)y`;}SHWgZ zb4g=+wDQz58PEC+s#>V1daZ zLgobq6WdN~C-n}_YXaRZWEW1pLr)8C3p+~IXepgff0)dqCheS^%e=&ad^%@Ol$^Yk zcUsu_9FApt|FOlZFP{>c`DA2h;27%Y#W4J2E+b6lb_o+q94w%lX&++J!`O#${E89| zVwuQ897dUTEnzv12!*8FIoRCq#a^Cw==})$>BXNTrg4B+iVUaew+|M5(quZ!U5bDs r#LUycQF03U4&ylYjLxHcjm}#%p6Jk+*N~3D2w#iGd- + + + + + + + \ No newline at end of file