├── LICENSE ├── README.md ├── scanfile.txt └── serializekiller.py /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SerializeKiller 2 | After the article published about the deserialization vulnerability we needed to scan all of our servers to verify if it's vulnerable. So we wrote this script, and decided to share it. This script enables you to scan a lot of servers in a short time for the famous Java deserialization vulnerability. It currently detects WebLogic, WebSphere, JBOSS and Jenkins. 3 | 4 | Edit: changed "infamous" to "famous" since this script made it somehow to Mr. Robot S03E07. 5 | 6 | ## What is the vulnerability? 7 | It is bad. The bug enables attackers to take over the the server, even without credentials. If you use Websphere, Weblogic, JBoss, Jenkins or OpenNMS you are probably vulnerable. 8 | 9 | You can read more about the bug here: http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/ 10 | 11 | ## How do I use it? 12 | You need to install Python 2, Curl and NMAP first. Also Python needs the requests library. With that installed it's pretty ease, just: 13 | `./serializekiller.py targets.txt` 14 | or 15 | `./serializekiller.py --url example.com` 16 | 17 | In the scanfile you can put IP adresses and hosts. It's also possible to scan specific ports. Please see the scanfile. 18 | 19 | **Note:** on my Mac I had to call the script with: `python2.7 serializekiller.py targets.txt`. It *might* be specific for my installation. On Linux we experienced no problems. 20 | 21 | ## Is it dangerous to use? 22 | 23 | No, it shouldn't do any damage, no exploit code is used. If you have doubts, keep in mind that being vulnerable is much worse ;) 24 | 25 | ## How fast is it? 26 | 27 | We scanned over a 1000 servers in less than 2 minutes. 28 | Edit: We noticed that in some cases it can be slower. 29 | 30 | ## Help, we are vulnerable! 31 | My colleague hacker Sijmen Ruwhof made a nice write-up what to do next. You can find it [here](http://sijmen.ruwhof.net/weblog/683-scanning-an-enterprise-organisation-for-the-critical-java-deserialization-vulnerability) 32 | 33 | ## Pfeew! We are not vulnerable! 34 | Congratz! But keep in mind that this script only scans some default ports. 35 | *E.g. If you have a vulnerable Jenkins server on port 80, the SerializeKiller won't find it.* 36 | If you want to scan non-default ports, you can specify those ports in the targets file. 37 | 38 | ## I've patched (some) of my servers. Will SerializeKiller detect that? 39 | Yes. And No. We couldn't find a way to verify a patched WebSphere server (OK, we could run the exploit, but thats not desirable). 40 | AFAIK it will detect a patched Jenkins, Jboss and Weblogic. 41 | *We decided to mark vulnerable WebSphere servers as possibly vulnerable, because we can't verify the patch.* 42 | 43 | ## I want to contribute 44 | Please send your pull request. 45 | 46 | ### Known issues 47 | - After specifing a port, it could take a long time to finish the scan. This is not a bug, it just takes a while. 48 | - Some SSL libs doesn't have the method create_default_context. As a result, it wont scan JBOSS and Jenkins proper. 49 | -------------------------------------------------------------------------------- /scanfile.txt: -------------------------------------------------------------------------------- 1 | foo.com 2 | bar.corp 3 | 127.0.0.1 4 | example.com 5 | xyz.local:80 -------------------------------------------------------------------------------- /serializekiller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ------------------------------------------------------------------------------ 3 | # Name: SerializeKiller 4 | # Purpose: Finding vulnerable java servers 5 | # 6 | # Author: (c) John de Kroon, 2015 7 | # Version: 1.0.2 8 | # ------------------------------------------------------------------------------ 9 | 10 | import subprocess 11 | import threading 12 | import time 13 | import socket 14 | import sys 15 | import argparse 16 | import urllib2 17 | import ssl 18 | 19 | from socket import error as socket_error 20 | from datetime import datetime 21 | import thread 22 | import time 23 | mutex = thread.allocate_lock() 24 | 25 | parser = argparse.ArgumentParser( 26 | prog='serializekiller.py', 27 | formatter_class=argparse.RawDescriptionHelpFormatter, 28 | description="Scan for Java Deserialization vulnerability.") 29 | parser.add_argument('--url', nargs='?', help="Scan a single URL") 30 | parser.add_argument('file', nargs='?', help='File with targets') 31 | args = parser.parse_args() 32 | 33 | 34 | def saveToFile(result): 35 | with open('result.txt', 'a') as f: 36 | f.write(result) 37 | f.close() 38 | 39 | def nmap(host, *args): 40 | global shellCounter 41 | global threads 42 | global target_list 43 | 44 | # All ports to enumerate over for jboss, jenkins, weblogic, websphere 45 | port_list = ['80', '81', '443', '444', '1099', '5005', 46 | '7001', '7002', '8080', '8081', '8083', '8443', 47 | '8880', '8888', '9000', '9080', '9443', '16200'] 48 | 49 | # Are there any ports defined for this host? 50 | if not target_list[host]: 51 | found = False 52 | cmd = 'nmap --host-timeout 5 --open -p %s %s' % (','.join(port_list), host) 53 | try: 54 | p = subprocess.Popen( 55 | cmd, 56 | stdout=subprocess.PIPE, 57 | stderr=subprocess.PIPE, 58 | shell=True) 59 | out, err = p.communicate() 60 | 61 | for this_port in port_list: 62 | if out.find(this_port) >= 0: 63 | if websphere(host, this_port) or weblogic(host, this_port) or jboss(host, this_port) or jenkins(host, this_port): 64 | found = True 65 | if found: 66 | shellCounter += 1 67 | except ValueError, v: 68 | print " ! Something went wrong on host: %s: %s" % (host, v) 69 | return 70 | else: 71 | for port in target_list[host]: 72 | if websphere( 73 | host, 74 | port) or weblogic( 75 | host, 76 | port) or jenkins( 77 | host, 78 | port) or jboss( 79 | host, 80 | port): 81 | shellCounter += 1 82 | return 83 | 84 | 85 | def websphere(url, port, retry=False): 86 | try: 87 | ctx = ssl.create_default_context() 88 | ctx.check_hostname = False 89 | ctx.verify_mode = ssl.CERT_NONE 90 | output = urllib2.urlopen( 91 | 'https://' + url + ":" + port, 92 | context=ctx, 93 | timeout=8).read() 94 | if "rO0AB" in output: 95 | mutex.acquire() 96 | print " - (possibly) Vulnerable Websphere: " + url + " (" + port + ")" 97 | saveToFile('[+] Websphere: ' + url + ':' + port + '\n') 98 | mutex.release() 99 | return True 100 | except urllib2.HTTPError as e: 101 | if e.getcode() == 500: 102 | if "rO0AB" in e.read(): 103 | mutex.acquire() 104 | print " - (possibly) Vulnerable Websphere: " + url + " (" + port + ")" 105 | saveToFile('[+] Websphere: ' + url + ':' + port + '\n') 106 | mutex.release() 107 | return True 108 | except: 109 | pass 110 | 111 | try: 112 | output = urllib2.urlopen( 113 | 'http://' + url + ":" + port, 114 | timeout=3).read() 115 | if "rO0AB" in output: 116 | mutex.acquire() 117 | print " - (possibly) Vulnerable Websphere: " + url + " (" + port + ")" 118 | saveToFile('[+] Websphere: ' + url + ':' + port + '\n') 119 | mutex.release() 120 | return True 121 | except urllib2.HTTPError as e: 122 | if e.getcode() == 500: 123 | if "rO0AB" in e.read(): 124 | mutex.acquire() 125 | print " - (possibly) Vulnerable Websphere: " + url + " (" + port + ")" 126 | saveToFile('[+] Websphere: ' + url + ':' + port + '\n') 127 | mutex.release() 128 | return True 129 | except: 130 | pass 131 | 132 | # Used this part from https://github.com/foxglovesec/JavaUnserializeExploits 133 | def weblogic(url, port): 134 | try: 135 | server_address = (url, int(port)) 136 | sock = socket.create_connection(server_address, 4) 137 | sock.settimeout(2) 138 | # Send headers 139 | headers = 't3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n' 140 | sock.sendall(headers) 141 | 142 | try: 143 | data = sock.recv(1024) 144 | except socket.timeout: 145 | return False 146 | 147 | sock.close() 148 | if "HELO" in data: 149 | mutex.acquire() 150 | print " - (possibly) Vulnerable Weblogic: " + url + " (" + str(port) + ")" 151 | saveToFile('[+] Weblogic: ' + url + ':' + str(port) + '\n') 152 | mutex.release() 153 | return True 154 | return False 155 | except socket_error: 156 | return False 157 | 158 | 159 | # Used something from https://github.com/foxglovesec/JavaUnserializeExploits 160 | def jenkins(url, port): 161 | try: 162 | cli_port = False 163 | ctx = ssl.create_default_context() 164 | ctx.check_hostname = False 165 | ctx.verify_mode = ssl.CERT_NONE 166 | try: 167 | output = urllib2.urlopen('https://'+url+':'+port+"/jenkins/", context=ctx, timeout=8).info() 168 | cli_port = int(output['X-Jenkins-CLI-Port']) 169 | except urllib2.HTTPError, e: 170 | if e.getcode() == 404: 171 | try: 172 | output = urllib2.urlopen('https://'+url+':'+port, context=ctx, timeout=8).info() 173 | cli_port = int(output['X-Jenkins-CLI-Port']) 174 | except: 175 | pass 176 | except: 177 | pass 178 | except: 179 | mutex.acquire() 180 | print " ! Could not check Jenkins on https. Maybe your SSL lib is broken." 181 | mutex.release() 182 | pass 183 | 184 | if cli_port is not True: 185 | try: 186 | output = urllib2.urlopen('http://'+url+':'+port+"/jenkins/", timeout=8).info() 187 | cli_port = int(output['X-Jenkins-CLI-Port']) 188 | except urllib2.HTTPError, e: 189 | if e.getcode() == 404: 190 | try: 191 | output = urllib2.urlopen('http://'+url+':'+port, timeout=8).info() 192 | cli_port = int(output['X-Jenkins-CLI-Port']) 193 | except: 194 | return False 195 | except: 196 | return False 197 | 198 | # Open a socket to the CLI port 199 | try: 200 | server_address = (url, cli_port) 201 | sock = socket.create_connection(server_address, 5) 202 | 203 | # Send headers 204 | headers = '\x00\x14\x50\x72\x6f\x74\x6f\x63\x6f\x6c\x3a\x43\x4c\x49\x2d\x63\x6f\x6e\x6e\x65\x63\x74' 205 | sock.send(headers) 206 | 207 | data1 = sock.recv(1024) 208 | if "rO0AB" in data1: 209 | mutex.acquire() 210 | print " - (possibly) Vulnerable Jenkins: " + url + " (" + str(port) + ")" 211 | saveToFile('[+] Weblogic: ' + url + ':' + str(port) + '\n') 212 | mutex.release() 213 | return True 214 | else: 215 | data2 = sock.recv(1024) 216 | if "rO0AB" in data2: 217 | mutex.acquire() 218 | print " - (possibly) Vulnerable Jenkins: " + url + " (" + str(port) + ")" 219 | saveToFile('[+] Jenkins: ' + ':' + str(port) + '\n') 220 | mutex.release() 221 | return True 222 | except: 223 | pass 224 | return False 225 | 226 | 227 | def jboss(url, port, retry=False): 228 | try: 229 | ctx = ssl.create_default_context() 230 | ctx.check_hostname = False 231 | ctx.verify_mode = ssl.CERT_NONE 232 | output = urllib2.urlopen( 233 | 'https://' + 234 | url + 235 | ':' + 236 | port + 237 | "/invoker/JMXInvokerServlet", 238 | context=ctx, 239 | timeout=8).read() 240 | except: 241 | try: 242 | output = urllib2.urlopen( 243 | 'http://' + 244 | url + 245 | ':' + 246 | port + 247 | "/invoker/JMXInvokerServlet", 248 | timeout=8).read() 249 | except: 250 | # OK. I give up. 251 | return False 252 | 253 | if "\xac\xed\x00\x05" in output: 254 | mutex.acquire() 255 | print " - (possibly) Vulnerable JBOSS: " + url + " (" + port + ")" 256 | saveToFile('[+] JBoss: ' + ':' + port + '\n') 257 | mutex.release() 258 | return True 259 | return False 260 | 261 | 262 | def urlStripper(url): 263 | url = str(url.replace("https:", '')) 264 | url = str(url.replace("http:", '')) 265 | url = str(url.replace("\r", '')) 266 | url = str(url.replace("\n", '')) 267 | url = str(url.replace("/", '')) 268 | return url 269 | 270 | 271 | def read_file(filename): 272 | f = open(filename) 273 | content = f.readlines() 274 | f.close() 275 | return content 276 | 277 | 278 | def worker(): 279 | global threads 280 | content = read_file(args.file) 281 | 282 | for line in content: 283 | if ":" in line: 284 | item = line.strip().split(':') 285 | if item[0] not in target_list: 286 | target_list[item[0]] = [item[1]] 287 | else: 288 | target_list[item[0]].append(item[1]) 289 | else: 290 | if line.strip() not in target_list: 291 | target_list[line.strip()] = [] 292 | 293 | print str(len(target_list)) + " targets found." 294 | total_jobs = len(target_list) 295 | current = 0 296 | 297 | for host in target_list: 298 | current += 1 299 | while threading.active_count() > threads: 300 | mutex.acquire() 301 | print " ! We have more threads running than allowed. Current: {} Max: {}.".format(threading.active_count(), threads) 302 | mutex.release() 303 | if threads < 100: 304 | threads += 1 305 | sys.stdout.flush() 306 | time.sleep(2) 307 | mutex.acquire() 308 | print " # Starting test {} of {} on {}.".format(current, total_jobs, host) 309 | sys.stdout.flush() 310 | mutex.release() 311 | threading.Thread(target=nmap, args=(host, False, 1)).start() 312 | 313 | # We're done! 314 | while threading.active_count() > 2: 315 | mutex.acquire() 316 | print " # Waiting for everybody to come back. Still {} active.".format(threading.active_count() - 1) 317 | sys.stdout.flush() 318 | mutex.release() 319 | time.sleep(4) 320 | 321 | mutex.acquire() 322 | print 323 | print " => scan done. " + str(shellCounter) + " vulnerable hosts found." 324 | print "Execution time: " + str(datetime.now() - startTime) 325 | mutex.release() 326 | exit() 327 | 328 | if __name__ == '__main__': 329 | startTime = datetime.now() 330 | mutex.acquire() 331 | print "Start SerializeKiller..." 332 | print "This could take a while. Be patient." 333 | print 334 | mutex.release() 335 | 336 | try: 337 | ssl.create_default_context() 338 | except: 339 | print " ! WARNING: Your SSL lib isn't supported. Results might be incomplete." 340 | pass 341 | 342 | target_list = {} 343 | shellCounter = 0 344 | if args.url: 345 | target_list[urlStripper(args.url)] = [] 346 | nmap(urlStripper(args.url)) 347 | elif args.file: 348 | threads = 30 349 | worker() 350 | else: 351 | mutex.acquire() 352 | print "ERROR: Specify a file or a url!" 353 | mutex.release() 354 | --------------------------------------------------------------------------------