├── .gitattributes ├── .gitignore ├── License ├── README.md └── pyssltest.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright (c) Mohesh Mohan (h4hacks.com). 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyssltest 2 | A python multithreaded script to make use of Qualys ssllabs api to test SSL flaws 3 | 4 | The information on how to use and other stuff will be published on the below URL 5 | 6 | http://www.h4hacks.com/2015/06/qualys-ssl-labs-api-multithreaded.html 7 | 8 | you can read more about the SSL labs API here 9 | 10 | https://github.com/ssllabs/ssllabs-scan/blob/master/ssllabs-api-docs.md 11 | 12 | 13 | Copyright (c) Mohesh Mohan (h4hacks.com). 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /pyssltest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) Mohesh Mohan (h4hacks.com). 4 | 5 | ''' 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | 12 | ''' 13 | 14 | # Completed standalone multi threaded script for SSL labs API - 18th June 2015, Mohesh Mohan 15 | import re, sys, getopt, argparse 16 | import unirest 17 | from mimetools import Message 18 | from StringIO import StringIO 19 | import json 20 | import csv 21 | import linecache 22 | from urlparse import urlparse 23 | import Queue 24 | import threading 25 | import time 26 | import random 27 | import os 28 | 29 | def testBit(int_type, offset): 30 | mask = 1 << offset 31 | return(int_type & mask) 32 | 33 | def parsetodomain(url): 34 | #dirty fix to match our dirty apps db :P 35 | if "http" not in url: 36 | url = "http://" + url 37 | pdomain = '' 38 | parsed_uri = urlparse(url) 39 | pdomain = '{uri.netloc}'.format(uri=parsed_uri) 40 | return (pdomain) 41 | 42 | def isURL(url): 43 | if "http" not in url: 44 | url = "http://" + url 45 | if "*" in url: 46 | return False 47 | regex = re.compile( 48 | r'^(?:http|ftp)s?://' # http:// or https:// 49 | r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain... 50 | r'localhost|' #localhost... 51 | r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip 52 | r'(?::\d+)?' # optional port 53 | r'(?:/?|[/?]\S+)$', re.IGNORECASE) 54 | return (regex.match(url)) 55 | 56 | def parseresults(result,mainapp,returncode): 57 | pdomain = mainapp 58 | print "\nParsing : " + str(mainapp) 59 | domain=ip=grade=sgrade=freak=logjam=poodletls=insecr=ccs=insecdh=ssl2=v2SuitesDisabled=poodlessl=weaks=tls12=ssl3=rc4=rc4Only=certchain=chaininc=crime=wrongd=certexp=fwsec=weakkey=eycert=selfcert=secreneg=tls10=tls11=tls12y='not tested' 60 | try: 61 | #domain 62 | try: 63 | domain=result['host'] 64 | except Exception: 65 | domain="error" 66 | pass 67 | #IP 68 | try: 69 | ip = result['endpoints'][0]['ipAddress'] 70 | except Exception: 71 | ip="JSON err" 72 | pass 73 | #grade 74 | try: 75 | grade = result['endpoints'][0]['grade'] 76 | except Exception: 77 | grade="JSON err" 78 | pass 79 | #sgrade 80 | try: 81 | sgrade = result['endpoints'][0]['gradeTrustIgnored'] 82 | except Exception: 83 | sgrade="JSON err" 84 | pass 85 | #freak 86 | try: 87 | freak= "N" 88 | if result['endpoints'][0]['details']['freak']== True: 89 | freak = "Y" 90 | except Exception,e: 91 | #print str(e) 92 | freak="JSON err" 93 | pass 94 | 95 | 96 | 97 | #Logjam 98 | try: 99 | logjam= "N" 100 | if result['endpoints'][0]['details']['logjam']== True: 101 | logjam = "Y" 102 | except Exception,e: 103 | #print str(e) 104 | logjam="JSON err" 105 | pass 106 | 107 | 108 | 109 | #PoodleTLS 110 | try: 111 | poodlevar = result['endpoints'][0]['details']['poodleTls'] 112 | if poodlevar == 2: 113 | poodletls ="Y" 114 | elif poodlevar == 1: 115 | poodletls = "N" 116 | else: 117 | poodletls = "fail" 118 | except Exception,e: 119 | #print str(e) 120 | poodletls="JSON err" 121 | pass 122 | #reneg suport 123 | try: 124 | insecr ="N" 125 | renegvar = int(result['endpoints'][0]['details']['renegSupport']) 126 | renegp = testBit(renegvar, 0) 127 | if renegp != 0: 128 | insecr ="Y" 129 | except Exception,e: 130 | #print str(e) 131 | insecr="JSON err" 132 | pass 133 | #ccs 134 | try: 135 | ccsvar = result['endpoints'][0]['details']['openSslCcs'] 136 | if ccsvar == 1: 137 | ccs ="N" 138 | elif ccsvar==3: 139 | ccs = "Y" 140 | else: 141 | ccs = "N" 142 | except Exception,e: 143 | #print str(e) 144 | ccs="JSON err" 145 | pass 146 | #insecdh 147 | try: 148 | insecdh= "N" 149 | for suite in result['endpoints'][0]['details']['suites']['list']: 150 | try: 151 | if "DHE" in suite['name']: 152 | try: 153 | if suite['q']==0: 154 | insecdh = "Y" 155 | #print "possible insec DH in " + suite['name'] 156 | except Exception,e: 157 | print "safe DHE :" + suite['name'] 158 | except Exception,e: 159 | #print str(e) 160 | pass 161 | except Exception,e: 162 | #print str(e) 163 | insecdh="JSON err" 164 | pass 165 | #ssl2 166 | try: 167 | ssl2= "N" 168 | v2SuitesDisabled= "Y" 169 | for protocol in result['endpoints'][0]['details']['protocols']: 170 | if "SSL" in protocol['name'] and protocol['version']=="2.0": 171 | ssl2 = "Y" 172 | if protocol['v2SuitesDisabled']== True: 173 | v2SuitesDisabled = "Y" 174 | else: 175 | v2SuitesDisabled = "N" 176 | 177 | 178 | except Exception,e: 179 | #print str(e) 180 | ssl2="JSON err" 181 | pass 182 | 183 | 184 | 185 | 186 | #tls12 187 | try: 188 | tls12= "Y" 189 | for protocol in result['endpoints'][0]['details']['protocols']: 190 | if "TLS" in protocol['name'] and protocol['version']=="1.2": 191 | tls12 = "N" 192 | except Exception,e: 193 | #print str(e) 194 | tls12="JSON err" 195 | pass 196 | #ssl3 197 | try: 198 | ssl3= "N" 199 | for protocol in result['endpoints'][0]['details']['protocols']: 200 | if "SSL" in protocol['name'] and protocol['version']=="3.0": 201 | ssl3 = "Y" 202 | except Exception,e: 203 | #print str(e) 204 | ssl3="JSON err" 205 | pass 206 | #rc4 207 | try: 208 | rc4var = result['endpoints'][0]['details']['supportsRc4'] 209 | if rc4var: 210 | rc4 ="Y" 211 | else: 212 | rc4 = "N" 213 | except Exception,e: 214 | #print str(e) 215 | rc4="JSON err" 216 | pass 217 | 218 | 219 | #rc4 Only 220 | try: 221 | rc4Onlyvar = result['endpoints'][0]['details']['rc4Only'] 222 | if rc4Onlyvar: 223 | rc4Only ="Y" 224 | else: 225 | rc4Only = "N" 226 | except Exception,e: 227 | #print str(e) 228 | rc4Only="JSON err" 229 | pass 230 | 231 | 232 | #weaks 233 | try: 234 | weaks="N" 235 | if "SHA1" in result['endpoints'][0]['details']['cert']['sigAlg']: 236 | weaks ="Y" 237 | except Exception,e: 238 | #print str(e) 239 | weaks="Err" 240 | pass 241 | #poodlessl 242 | try: 243 | poodlessl="N" 244 | if result['endpoints'][0]['details']['poodle']== True: 245 | poodlessl="Y" 246 | except Exception,e: 247 | #print str(e) 248 | poodlessl="Err" 249 | pass 250 | #certchain 251 | try: 252 | certchain="N" 253 | certmsg = int(result['endpoints'][0]['details']['cert']['issues']) 254 | chainp = testBit(certmsg, 0) 255 | if chainp != 0 or int(result['endpoints'][0]['details']['chain']['issues']) !=0 : 256 | certchain ="Y" 257 | except Exception,e: 258 | #print str(e) 259 | certchain="Err" 260 | pass 261 | #chainincomp 262 | try: 263 | chaininc="N" 264 | certmsg1 = int(result['endpoints'][0]['details']['chain']['issues']) 265 | chainp1 = testBit(certmsg1, 1) 266 | if chainp1 != 0 : 267 | chaininc ="Y" 268 | except Exception,e: 269 | #print str(e) 270 | chaininc="Err" 271 | pass 272 | #wrongd 273 | try: 274 | wrongd="N" 275 | certmsg = int(result['endpoints'][0]['details']['cert']['issues']) 276 | chainp = testBit(certmsg, 3) 277 | if chainp != 0: 278 | wrongd ="Y" 279 | except Exception,e: 280 | #print str(e) 281 | wrongd="Err" 282 | pass 283 | #selfcert 284 | try: 285 | selfcert="N" 286 | certmsg = int(result['endpoints'][0]['details']['cert']['issues']) 287 | chainp = testBit(certmsg, 6) 288 | if chainp != 0: 289 | selfcert ="Y" 290 | except Exception,e: 291 | #print str(e) 292 | selfcert="Err" 293 | pass 294 | #certexp 295 | try: 296 | certexp="N" 297 | certmsg = int(result['endpoints'][0]['details']['cert']['issues']) 298 | ntbef = testBit(certmsg, 1) 299 | ntaf = testBit(certmsg, 2) 300 | if ntbef != 0 or ntaf != 0: 301 | certexp ="Y" 302 | except Exception,e: 303 | #print str(e) 304 | certexp="Err" 305 | pass 306 | #crime 307 | try: 308 | crime="N" 309 | if result['endpoints'][0]['details']['compressionMethods']!= 0 and result['endpoints'][0]['details']['supportsNpn'] == False: 310 | crime ="Y" 311 | except Exception,e: 312 | #print str(e) 313 | crime="Err" 314 | pass 315 | #eycert 316 | try: 317 | eycert="N" 318 | if "EY Issuing" in result['endpoints'][0]['details']['cert']['issuerLabel']: 319 | eycert ="Y" 320 | except Exception,e: 321 | #print str(e) 322 | eycert="Err" 323 | pass 324 | #secreneg 325 | try: 326 | secreneg="N" 327 | certmsg = int(result['endpoints'][0]['details']['renegSupport']) 328 | secre = testBit(certmsg, 1) 329 | if secre != 0: 330 | secreneg ="Y" 331 | except Exception,e: 332 | #print str(e) 333 | secreneg="Err" 334 | pass 335 | #fwsec inverted logic :P 336 | try: 337 | fwsec="Y" 338 | fwsecvar = result['endpoints'][0]['details']['forwardSecrecy'] 339 | fws = testBit(fwsecvar, 2) 340 | if fws != 0: 341 | fwsec ="N" 342 | except Exception,e: 343 | #print str(e) 344 | fwsec="JSON err" 345 | pass 346 | #weakkey 347 | try: 348 | weakkey="N" 349 | if (("SA" in result['endpoints'][0]['details']['key']['alg']) and int(result['endpoints'][0]['details']['key']['size'])<2048) or (("EC" in result['endpoints'][0]['details']['key']['alg']) and int(result['endpoints'][0]['details']['key']['size'])<256) : 350 | weakkey ="Y" 351 | except Exception,e: 352 | weakkey="N" 353 | pass 354 | #tls10 355 | try: 356 | tls10= "N" 357 | for protocol in result['endpoints'][0]['details']['protocols']: 358 | if "TLS" in protocol['name'] and protocol['version']=="1.0": 359 | tls10 = "Y" 360 | except Exception,e: 361 | #print str(e) 362 | tls10="JSON err" 363 | pass 364 | #tls10 365 | try: 366 | tls11= "N" 367 | for protocol in result['endpoints'][0]['details']['protocols']: 368 | if "TLS" in protocol['name'] and protocol['version']=="1.1": 369 | tls11 = "Y" 370 | except Exception,e: 371 | #print str(e) 372 | tls11="JSON err" 373 | pass 374 | #tls10 375 | try: 376 | tls12y= "N" 377 | for protocol in result['endpoints'][0]['details']['protocols']: 378 | if "TLS" in protocol['name'] and protocol['version']=="1.2": 379 | tls12y = "Y" 380 | except Exception,e: 381 | #print str(e) 382 | tls12y="JSON err" 383 | pass 384 | # write deal details to CSV 385 | #thumb print 386 | try: 387 | thumbp = result['endpoints'][0]['details']['cert']['sha1Hash'] 388 | except Exception,e: 389 | #print str(e) 390 | thumbp="JSON err" 391 | pass 392 | 393 | #common names 394 | try: 395 | commonnames= "" 396 | for common in result['endpoints'][0]['details']['cert']['commonNames']: 397 | commonnames = " ".join((commonnames, common)) 398 | except Exception,e: 399 | #print str(e) 400 | commonnames="JSON err" 401 | pass 402 | 403 | #alternate names 404 | try: 405 | altnames= "" 406 | for alt in result['endpoints'][0]['details']['cert']['altNames']: 407 | altnames = " ".join((altnames, alt)) 408 | except Exception,e: 409 | #print str(e) 410 | altnames="JSON err" 411 | pass 412 | 413 | #drown 414 | try: 415 | drown="N" 416 | if result['endpoints'][0]['details']['drownErrors'] == False and result['endpoints'][0]['details']['drownVulnerable'] == True : 417 | drown ="Y" 418 | elif result['endpoints'][0]['details']['drownErrors'] == True : 419 | drown = "Assessment error" 420 | except Exception,e: 421 | print str(e) 422 | drown="Err" 423 | pass 424 | 425 | 426 | # For error values 427 | #No SSL 428 | try: 429 | if result['endpoints'][0]['statusMessage'] =="No secure protocols supported": 430 | grade = "No SSL/TLS" 431 | sgrade=freak=logjam=poodletls=insecr=ccs=insecdh=ssl2=v2SuitesDisabled=poodlessl=weaks=tls12=ssl3=rc4=rc4Only=certchain=chaininc=crime=wrongd=certexp=fwsec=weakkey=eycert=selfcert=secreneg=tls10=tls11=tls12y=thumbp=commonnames=altnames=drown='NA' 432 | except Exception,e: 433 | pass 434 | #No DNS 435 | try: 436 | if result['statusMessage'] =="Unable to resolve domain name": 437 | grade = "No DNS" 438 | sgrade=freak=logjam=poodletls=insecr=ccs=insecdh=ssl2=v2SuitesDisabled=poodlessl=weaks=tls12=ssl3=rc4=rc4Only=certchain=chaininc=crime=wrongd=certexp=fwsec=weakkey=eycert=selfcert=secreneg=tls10=tls11=tls12y=thumbp=commonnames=altnames=drown='NA' 439 | except Exception,e: 440 | pass 441 | #Unknown errors from server stupid stuff. not optimal need to work on this 442 | try: 443 | if "Unable" in result['endpoints'][0]['statusMessage']: 444 | grade = result['endpoints'][0]['statusMessage'] 445 | sgrade=freak=logjam=poodletls=insecr=ccs=insecdh=ssl2=v2SuitesDisabled=poodlessl=weaks=tls12=ssl3=rc4=rc4Only=certchain=chaininc=crime=wrongd=certexp=fwsec=weakkey=eycert=selfcert=secreneg=tls10=tls11=tls12y=thumbp=commonnames=altnames=drown='Error' 446 | except Exception,e: 447 | pass 448 | try: 449 | if "Failed" in result['endpoints'][0]['statusMessage']: 450 | grade = result['endpoints'][0]['statusMessage'] 451 | sgrade=freak=logjam=poodletls=insecr=ccs=insecdh=ssl2=v2SuitesDisabled=poodlessl=weaks=tls12=ssl3=rc4=rc4Only=certchain=chaininc=crime=wrongd=certexp=fwsec=weakkey=eycert=selfcert=secreneg=tls10=tls11=tls12y=thumbp=commonnames=altnames=drown='Error' 452 | except Exception,e: 453 | pass 454 | try: 455 | if "RFC 1918" in result['endpoints'][0]['statusMessage']: 456 | grade = result['endpoints'][0]['statusMessage'] 457 | sgrade=freak=logjam=poodletls=insecr=ccs=insecdh=ssl2=v2SuitesDisabled=poodlessl=weaks=tls12=ssl3=rc4=rc4Only=ertchain=chaininc=crime=wrongd=certexp=fwsec=weakkey=eycert=selfcert=secreneg=tls10=tls11=tls12y=thumbp=commonnames=altnames=drown='Error' 458 | except Exception,e: 459 | pass 460 | 461 | try: 462 | if "Unexpected" in result['endpoints'][0]['statusMessage']: 463 | grade = result['endpoints'][0]['statusMessage'] 464 | sgrade=freak=logjam=poodletls=insecr=ccs=insecdh=ssl2=v2SuitesDisabled=poodlessl=weaks=tls12=ssl3=rc4=rc4Only=certchain=chaininc=crime=wrongd=certexp=fwsec=weakkey=eycert=selfcert=secreneg=tls10=tls11=tls12y=thumbp=commonnames=altnames=drown='Error' 465 | except Exception,e: 466 | pass 467 | try: 468 | if "Internal error" in result['endpoints'][0]['statusMessage']: 469 | grade = result['endpoints'][0]['statusMessage'] 470 | sgrade=freak=logjam=poodletls=insecr=ccs=insecdh=ssl2=v2SuitesDisabled=poodlessl=weaks=tls12=ssl3=rc4=rc4Only=certchain=chaininc=crime=wrongd=certexp=fwsec=weakkey=eycert=selfcert=secreneg=tls10=tls11=tls12y=thumbp=commonnames=altnames=drown='Error' 471 | except Exception,e: 472 | pass 473 | try: 474 | if "Internal Error" in result['endpoints'][0]['statusMessage']: 475 | grade = result['endpoints'][0]['statusMessage'] 476 | sgrade=freak=logjam=poodletls=insecr=ccs=insecdh=ssl2=v2SuitesDisabled=poodlessl=weaks=tls12=ssl3=rc4=rc4Only=certchain=chaininc=crime=wrongd=certexp=fwsec=weakkey=eycert=selfcert=secreneg=tls10=tls11=tls12y=thumbp=commonnames=altnames=drown='Error' 477 | except Exception,e: 478 | pass 479 | 480 | try: 481 | if result['status'] =="ERROR" and result['statusMessage'] !="Unable to resolve domain name": 482 | grade = "Error from server, need manual tests" 483 | sgrade=freak=logjam=poodletls=insecr=ccs=insecdh=ssl2=v2SuitesDisabled=poodlessl=weaks=tls12=ssl3=rc4=rc4Only=certchain=chaininc=crime=wrongd=certexp=fwsec=weakkey=eycert=selfcert=secreneg=tls10=tls11=tls12y=thumbp=commonnames=altnames=drown='Error' 484 | except Exception,e: 485 | pass 486 | 487 | row = "" 488 | row = pdomain,domain,ip,'','','','','','','','','',returncode,grade,sgrade,'','','','',drown,freak,logjam,poodletls,insecr,ccs,insecdh,ssl2,v2SuitesDisabled,poodlessl,wrongd,certexp,eycert,selfcert,tls12,ssl3,rc4,rc4Only,certchain,chaininc,crime,fwsec,weakkey,weaks,secreneg,tls10,tls11,tls12y,'','','','','',thumbp,commonnames,altnames 489 | print "Parsed: " + str(mainapp) 490 | return(row) 491 | 492 | except Exception,e: 493 | row = pdomain,domain,'Failed','','','','','','','','','',returncode,grade,sgrade,'','','','',drown,freak,logjam,poodletls,insecr,ccs,insecdh,ssl2,v2SuitesDisabled,poodlessl,wrongd,certexp,eycert,selfcert,tls12,ssl3,rc4,rc4Only,certchain,chaininc,crime,fwsec,weakkey,weaks,secreneg,tls10,tls11,tls12y,'','','','','','','','' 494 | return(row) 495 | 496 | newjob = False 497 | mainapps = [] 498 | jobtrack = {} 499 | parser = argparse.ArgumentParser(description='SSL labs mass test tool', add_help=True, epilog='SSL labs mass test tool. Made by Mohesh Mohan for Global info sec, security audit team (PCC). Ernst & Young, 2015') 500 | parser.add_argument('-i','--input', help='The input file with list of URLs', required=True) 501 | parser.add_argument('-o','--output', help='Output csv file', required=True) 502 | parser.add_argument('-n','--new', help='Start new assesment, dont use cache', required=False, action='store_true') 503 | argsdict = vars(parser.parse_args()) 504 | 505 | if not os.path.exists(os.getcwd() + "\\results"): 506 | os.makedirs(os.getcwd()+ "\\results") 507 | 508 | newjob = argsdict['new'] 509 | in_file = argsdict['input'] 510 | 511 | line_no = 1 512 | diter = "" 513 | read_line = linecache.getline(in_file, line_no).rstrip() 514 | 515 | while read_line is not "": 516 | line_no = line_no + 1 517 | mainapps.append(read_line) 518 | diter = parsetodomain(read_line) 519 | if isURL(read_line): 520 | jobtrack[diter] = "Not Tested" 521 | else: 522 | jobtrack[diter] = "Invalid" 523 | print read_line + " is invalid" 524 | read_line = linecache.getline(in_file, line_no).rstrip() 525 | 526 | total_lines = line_no - 1 527 | 528 | print "There are %d Urls read from the File" % (total_lines) 529 | 530 | #print mainapps 531 | print "\n No of URLs is identified is " + str(len(mainapps)) 532 | #print "\n" 533 | #print jobtrack 534 | print "\n No of domains is " + str(len(jobtrack)) 535 | 536 | raw_input("Press Enter to continue...") 537 | 538 | apipath = "https://api.ssllabs.com/api/v2/" 539 | clientheaders = { "User-Agent": "pyssltest 1.3"} 540 | 541 | infocommand = apipath + "info" 542 | analyzecommand = apipath + "analyze" 543 | getdatacommand = apipath + "getEndpointData" 544 | 545 | if newjob == True: 546 | pstart = {"publish" : "off", "ignoreMismatch" : "on", "all" : "done", "host" : "", "startNew" : "on"} 547 | else: 548 | pstart = {"publish" : "off", "ignoreMismatch" : "on", "all" : "done", "host" : "", "fromCache" : "on"} 549 | prepeat = {"publish" : "off", "ignoreMismatch" : "on", "all" : "done", "host" : ""} 550 | pinfo = {} 551 | 552 | #response = unirest.get(infocommand, headers=clientheaders, params=pinfo) 553 | #headers1 = Message(StringIO(response.headers)) 554 | #print response.code # The HTTP status code 555 | #print response.headers 556 | #print headers1['X-Max-Assessments'] # The HTTP headers 557 | #print str(response.body) # The parsed response 558 | #print "raw follows" 559 | #print response.raw_body # The unparsed response 560 | 561 | def status(): 562 | ready = 0 563 | inva = 0 564 | errc = 0 565 | pend = 0 566 | print '\n' * 100 567 | for key in jobtrack: 568 | if jobtrack[key] =="Not Tested": 569 | pend = pend + 1 570 | elif jobtrack[key] =="Invalid": 571 | inva = inva + 1 572 | elif jobtrack[key] =="ERROR": 573 | errc = errc + 1 574 | elif jobtrack[key] =="READY": 575 | ready = ready + 1 576 | print "\n There are " + str(pend) + " pending" 577 | print "\n There are " + str(inva) + " Invalid" 578 | print "\n There are " + str(errc) + " errors" 579 | print "\n There are " + str(ready) + " ready" 580 | print "\n There are " + str(threading.activeCount()) + " Threads" 581 | 582 | def runscan(q): 583 | while not q.empty(): 584 | mydata = threading.local() 585 | mydata.scanning = True 586 | mydata.dom = q.get() # get the item from the queue 587 | mydata.pstart = pstart 588 | mydata.prepeat = prepeat 589 | print "Now Processing " + str(mydata.dom) 590 | #print "\n Active threads :" + str(threading.activeCount()) 591 | while (mydata.scanning): 592 | try: 593 | status() 594 | 595 | # if for some unknown reason a thread just don't die, lets kill it 596 | if jobtrack[mydata.dom] != "Scanning" and jobtrack[mydata.dom] != "Not Tested": 597 | q.task_done() 598 | mydata.scanning = False 599 | 600 | # Check if scan is initiated for this one 601 | if jobtrack[mydata.dom] =="Not Tested": 602 | mydata.pstart['host'] = mydata.dom 603 | mydata.response = unirest.get(analyzecommand, headers=clientheaders, params=mydata.pstart) 604 | jobtrack[mydata.dom] = "Scanning" 605 | else: 606 | mydata.prepeat['host'] = mydata.dom 607 | mydata.response = unirest.get(analyzecommand, headers=clientheaders, params=mydata.prepeat) 608 | # did we get any valid response? 609 | if 'status' in mydata.response.body: 610 | if mydata.response.body['status'] == "READY" or mydata.response.body['status'] =="ERROR" : 611 | mydata.host = mydata.response.body['host'] 612 | #print "Response is for " + mydata.host 613 | #print "Dom is " + mydata.dom 614 | if mydata.host == mydata.dom: 615 | jobtrack[mydata.host] = mydata.response.body['status'] 616 | mydata.fo = open("results/"+mydata.host+".txt", "wb") 617 | #mydata.fo.write(mydata.response.raw_body); 618 | json.dump(mydata.response.body,mydata.fo) 619 | # Close opend file 620 | mydata.fo.close() 621 | q.task_done() 622 | mydata.scanning = False 623 | print mydata.host + " is " + mydata.response.body['status'] 624 | mydata.headers1 = Message(StringIO(mydata.response.headers)) 625 | if mydata.response.code == 429: 626 | print "\nThread: " + str(mydata.dom) +" to sleep 10 secs due to 429 from server" 627 | time.sleep(10) 628 | elif mydata.response.code == 503 or mydata.response.code == 529: 629 | print "\n server overload, Response code is :" + str(mydata.response.code) 630 | randtime = random.randrange(100, 500) 631 | print "\n Sleeping for :" + str(randtime) 632 | time.sleep(randtime) 633 | time.sleep(3) # delays for 5 seconds 634 | #print headers1['X-Max-Assessments'] 635 | except Exception,e: 636 | print str(e) 637 | try: 638 | if 'errors' in mydata.response.body: 639 | jobtrack[mydata.dom] = mydata.response.body['status'] 640 | mydata.fo = open("results/"+mydata.dom+".txt", "wb") 641 | json.dump(mydata.response.body,mydata.fo) 642 | mydata.fo.close() 643 | q.task_done() 644 | mydata.scanning = False 645 | except Exception,e: 646 | print str(e) 647 | pass 648 | pass 649 | 650 | 651 | 652 | 653 | q = Queue.LifoQueue() 654 | #put items to queue 655 | for key in jobtrack: 656 | if jobtrack[key] != "Invalid": 657 | q.put(str(key)) 658 | else: 659 | print str(key) + " is not added to queue as its invalid" 660 | 661 | for i in range(100): 662 | t1 = threading.Thread(target=runscan,args=(q,)) 663 | t1.daemon = True 664 | t1.start() # start the thread 665 | 666 | q.join() 667 | 668 | print "\nFinally" 669 | print jobtrack 670 | csvw = csv.writer(open(argsdict['output'], 'wb')) 671 | csvw.writerow(['Input_URL', 'Domain','IP','Common Name','Source','Date Added','Portfolio','sub portfolio','ELT', 'POC','IT Contact','Status','returncode', 'Grade','Secondary grade','Expected Remediation Date','Grade After Remediation','Actual Remediation Date','Actual Grade after Remediation','Drown (Experimental)','Freak','Logjam','Poodle_TLS','Insecure renegotiation','OpenSSL ccs','Insecure DH','SSL v2','SSLv2 SuitesDisabled','Poodle_SSL','wrong domain', 'cert expired','Ey issued cert','self signed cert','No TLS1.2?', 'SSL v3', 'RC4','rc4Only', 'cert chain issue','Cert chain incomplete', 'CRIME', 'forward secrecy not supported?', 'weak private key?','weak signature','secure renegotiation', 'TLS 1.0','TLS 1.1','TLS 1.2','Recommendation to raise score to A', 'Date Last scanned(Auto)','Date Manually validated','Comments', 'Audit teams Comments(New grade as on *)','Thumbprint','common names', 'alternate names' ]) 672 | 673 | for app in mainapps: 674 | try: 675 | curdom = parsetodomain(app) 676 | resfile = open("results/"+curdom+".txt", "r") 677 | jsonresults = json.load(resfile) 678 | resfile.close() 679 | row = parseresults(jsonresults,app,jobtrack[curdom]) 680 | csvw.writerow(row) 681 | except Exception,e: 682 | print str(e) 683 | csvw.writerow([app,curdom ,str(e),jobtrack[curdom], 'Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error', 'Error','Error','Error','Error', 'Error', 'Error', 'Error', 'Error', 'Error', 'Error','Error','Error', 'Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error','Error' ]) 684 | pass --------------------------------------------------------------------------------