YeiZeta

Patator python

Oct 2nd, 2012
266
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 95.83 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. # Copyright (C) 2011 Sebastien MACKE
  4. #
  5. # This program is free software; you can redistribute it and/or modify it under
  6. # the terms of the GNU General Public License version 2, as published by the
  7. # Free Software Foundation
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  12. # details (http://www.gnu.org/licenses/gpl.txt).
  13.  
  14. __author__ = 'Sebastien Macke'
  15. __email__ = '[email protected]'
  16. __url__ = 'http://www.hsc.fr/ressources/outils/patator/'
  17. __git__ = 'http://code.google.com/p/patator/'
  18. __version__ = '0.3'
  19. __license__ = 'GPLv2'
  20.  
  21. # README {{{
  22.  
  23. '''
  24. INTRODUCTION
  25. ------------
  26.  
  27. * What ?
  28.  
  29. Patator is a multi-purpose brute-forcer, with a modular design and a flexible usage.
  30.  
  31. Currently it supports the following modules:
  32. - ftp_login : Brute-force FTP
  33. - ssh_login : Brute-force SSH
  34. - telnet_login : Brute-force Telnet
  35. - smtp_login : Brute-force SMTP
  36. - smtp_vrfy : Enumerate valid users using the SMTP 'VRFY' command
  37. - smtp_rcpt : Enumerate valid users using the SMTP 'RCPT TO' command
  38. - http_fuzz : Brute-force HTTP/HTTPS
  39. - pop_passd : Brute-force poppassd (not POP3)
  40. - ldap_login : Brute-force LDAP
  41. - smb_login : Brute-force SMB
  42. - mssql_login : Brute-force MSSQL
  43. - oracle_login : Brute-force Oracle
  44. - mysql_login : Brute-force MySQL
  45. - pgsql_login : Brute-force PostgreSQL
  46. - vnc_login : Brute-force VNC
  47.  
  48. - dns_forward : Forward lookup subdomains
  49. - dns_reverse : Reverse lookup subnets
  50. - snmp_login : Brute-force SNMPv1/2 and SNMPv3
  51.  
  52. - unzip_pass : Brute-force the password of encrypted ZIP files
  53. - keystore_pass : Brute-force the password of Java keystore files
  54.  
  55. Future modules to be implemented:
  56. - rdp_login
  57. - vmware_login (902/tcp)
  58. - pop3_login
  59.  
  60. The name "Patator" comes from http://www.youtube.com/watch?v=xoBkBvnTTjo
  61. "Whatever the payload to fire, always use the same launch tube"
  62.  
  63. * Why ?
  64.  
  65. Basically, I got tired of using Medusa, Hydra, ncrack, metasploit auxiliary modules, nmap NSE scripts and the like because:
  66. - they either do not work or are not reliable (got me false negatives several times in the past)
  67. - they are slow (not multi-threaded or not testing multiple passwords within the same TCP connection)
  68. - they lack very useful features that are easy to code in python (eg. interactive runtime)
  69.  
  70.  
  71. FEATURES
  72. --------
  73. * No false negatives, as it is the user that decides what results to ignore based on:
  74. + status code of response
  75. + size of response
  76. + matching string or regex in response data
  77. + ... see --help
  78.  
  79. * Modular design
  80. + not limited to network modules (eg. the unzip_pass module)
  81. + not limited to brute-forcing (eg. remote exploit testing, or vulnerable version probing)
  82.  
  83. * Interactive runtime
  84. + show verbose progress
  85. + pause/unpause execution
  86. + increase/decrease verbosity
  87. + add new actions & conditions during runtime in order to exclude more types of response from showing
  88. + ... press h to see all available interactive commands
  89.  
  90. * Use persistent connections (ie. will test several passwords until the server disconnects)
  91.  
  92. * Multi-threaded
  93.  
  94. * Flexible user input
  95. - Any part of a payload is fuzzable:
  96. + use FILE[0-9] keywords to iterate on a file
  97. + use COMBO[0-9] keywords to iterate on the combo entries of a file
  98. + use NET[0-9] keywords to iterate on every host of a network subnet
  99.  
  100. - Iteration over the joined wordlists may be done in any order
  101.  
  102. * Save every response (along with request) to seperate log files for later reviewing
  103.  
  104.  
  105. INSTALL
  106. -------
  107.  
  108. * Dependencies (best tested versions)
  109.  
  110. | Required for | URL | Version |
  111. --------------------------------------------------------------------------------------------------
  112. paramiko | SSH | http://www.lag.net/paramiko/ | 1.7.7.1 |
  113. --------------------------------------------------------------------------------------------------
  114. pycurl | HTTP | http://pycurl.sourceforge.net/ | 7.19.0 |
  115. --------------------------------------------------------------------------------------------------
  116. openldap | LDAP | http://www.openldap.org/ | 2.4.24 |
  117. --------------------------------------------------------------------------------------------------
  118. impacket | SMB | http://oss.coresecurity.com/projects/impacket.html | svn#414 |
  119. --------------------------------------------------------------------------------------------------
  120. cx_Oracle | Oracle | http://cx-oracle.sourceforge.net/ | 5.0.4 |
  121. --------------------------------------------------------------------------------------------------
  122. mysql-python | MySQL | http://sourceforge.net/projects/mysql-python/ | 1.2.3 |
  123. --------------------------------------------------------------------------------------------------
  124. psycopg | PostgreSQL | http://initd.org/psycopg/ | 2.4.1 |
  125. --------------------------------------------------------------------------------------------------
  126. pycrypto | VNC | http://www.dlitz.net/software/pycrypto/ | 2.3 |
  127. --------------------------------------------------------------------------------------------------
  128. pydns | DNS | http://pydns.sourceforge.net/ | 2.3.4 |
  129. --------------------------------------------------------------------------------------------------
  130. pysnmp | SNMP | http://pysnmp.sf.net/ | 4.1.16a |
  131. --------------------------------------------------------------------------------------------------
  132. IPy | NETx keywords | https://github.com/haypo/python-ipy | 0.75 |
  133. --------------------------------------------------------------------------------------------------
  134. unzip | ZIP passwords | http://www.info-zip.org/ | 6.0 |
  135. --------------------------------------------------------------------------------------------------
  136. Java | keystore files | http://www.oracle.com/technetwork/java/javase/ | 6u29 |
  137. --------------------------------------------------------------------------------------------------
  138. python | | http://www.python.org/ | 2.6.6 |
  139. --------------------------------------------------------------------------------------------------
  140.  
  141. * Shortcuts (optionnal)
  142. ln -s path/to/patator.py /usr/bin/ftp_login
  143. ln -s path/to/patator.py /usr/bin/http_fuzz
  144. so on ...
  145.  
  146.  
  147. USAGE
  148. -----
  149.  
  150. $ python patator.py <module> -h
  151. or
  152. $ <module> -h (if you created the shortcuts)
  153.  
  154. There are global options and module options:
  155. - all global options start with - or --
  156. - all module options are of the form option=value
  157.  
  158. All module options are fuzzable:
  159. ---------
  160. ./module host=FILE0 port=FILE1 foobar=FILE2.google.FILE3 0=hosts.txt 1=ports.txt 2=foo.txt 3=bar.txt
  161.  
  162. The keywords (FILE, COMBO, NET, ...) act as place-holders. They indicate the type of wordlist
  163. and where to replace themselves with the actual words to test.
  164.  
  165. Each keyword is numbered in order to:
  166. - match the corresponding wordlist
  167. - and indicate in what order to iterate over all the wordlists
  168.  
  169. For instance, this would be the classic order:
  170. ---------
  171. ./module host=FILE0 user=FILE1 password=FILE2 0=hosts.txt 1=logins.txt 2=passwords.txt
  172. 10.0.0.1 root password
  173. 10.0.0.1 root 123456
  174. 10.0.0.1 root qsdfghj
  175. ....
  176. 10.0.0.1 test password
  177. 10.0.0.1 test 123456
  178. 10.0.0.1 test qsdfghj
  179. ...
  180. 10.0.0.2 root password
  181. ...
  182.  
  183. When a better way may be:
  184. ---------
  185. ./module host=FILE2 password=FILE1 user=FILE0 0=logins.txt 1=passwords.txt 2=hosts.txt
  186. 10.0.0.1 root password
  187. 10.0.0.2 root password
  188. 10.0.0.1 admin password
  189. 10.0.0.2 admin password
  190. 10.0.0.1 root 123456
  191. 10.0.0.2 root 123456
  192. 10.0.0.1 admin 123456
  193. ...
  194.  
  195.  
  196. * Keywords
  197.  
  198. Brute-force a list of hosts with a file containing combo entries (each line := login:password).
  199. ---------
  200. ./module host=FILE0 user=COMBO10 password=COMBO11 0=hosts.txt 1=combos.txt
  201.  
  202.  
  203. Scan subnets to just grab version banners.
  204. ---------
  205. ./module host=NET0 0=10.0.1.0/24,10.0.2.0/24,10.0.3.128-10.0.3.255
  206.  
  207.  
  208. * Actions & Conditions
  209.  
  210. Use the -x option to do specific actions upon receiving expected results. For instance:
  211.  
  212. To ignore responses with status code 301 *AND* a size within a range.
  213. ---------
  214. ./module host=10.0.0.1 user=FILE0 -x ignore:code=301,size=57-74
  215.  
  216. To ignore responses with status code 500 *OR* containing "Internal error".
  217. ---------
  218. ./module host=10.0.0.1 user=FILE0 -x ignore:code=500 -x ignore:fgrep='Internal error'
  219.  
  220. Remember that conditions are ANDed within the same -x option, use multiple -x options to
  221. specify ORed conditions.
  222.  
  223.  
  224. * Failures (--failure-delay and --max-retries options)
  225.  
  226. During execution, failures may happen, such as a TCP connect timeout for
  227. instance. A failure is actually an exception that is not caught by the module,
  228. and as a result the exception is caught upstream by the controller. By default,
  229. such exceptions, or failures, are not reported to the user, the controller will
  230. try 5 more times before reporting the failed payload with the code "xxx"
  231. (--max-retries defaults to 5).
  232.  
  233. After catching a failure, the controller will discard the module instance that
  234. may be in a dubious state to create a brand new one, and then sleep for 0.5
  235. second before trying again the same payload (--failure-delay defaults to 0.5).
  236.  
  237.  
  238. * Read carefully the following examples to get a good understanding of how patator works.
  239. {{{ FTP
  240.  
  241. * Brute-force authentication.
  242. (a) Establish a new TCP connection for every login attempt (slow).
  243. --------- (a)
  244. ftp_login host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt persistent=0
  245.  
  246. NB. If you get errors like "too many connections from your IP address", try
  247. decreasing the number of threads, the server may be enforcing a maximum
  248. number of concurrent connections.
  249.  
  250.  
  251. * Same as before, but without persistent=0 in order to re-use the TCP connection (faster).
  252. (a) Establish a new TCP connection after 3 login attempts were done using the same TCP connection.
  253. (b) Do not report wrong passwords.
  254. (c) Reconnect when a valid password is found (need to logoff before testing other passwords).
  255. --------- (a) (b) (c)
  256. ftp_login ... --rate-reset 3 -x ignore:mesg='Login incorrect.' -x reset:fgrep='Login successful'
  257.  
  258.  
  259. * Same as before, but without --rate-reset as we automatically detect when the server has
  260. closed the connection.
  261. (a) Do not report everytime the server shuts down the TCP connection, reconnect and
  262. retry last login/password.
  263. (b) Exit execution as soon as a valid password is found.
  264. --------- (a) (b)
  265. ftp_login ... -x ignore,reset,retry:code=500 -x quit:fgrep='Login successful'
  266.  
  267.  
  268. * Find anonymous FTP servers on a subnet.
  269. ---------
  270. ftp_login host=NET0 user=anonymous [email protected] 0=10.0.0.0/24
  271.  
  272. }}}
  273. {{{ SSH
  274. * Brute-force authentication.
  275. (a) Test 3 passwords within the same SSH session before reconnecting.
  276. (b) Reconnect when a valid password is found (need to logoff before testing other passwords).
  277. --------- (a) (b)
  278. ssh_login host=10.0.0.1 user=root password=FILE0 0=passwords.txt --rate-reset 3 -x reset:code=0
  279.  
  280. NB. If you get errors like "Error reading SSH protocol banner ... Connection reset by peer",
  281. try decreasing the number of threads, the server may be enforcing a maximum
  282. number of concurrent connections (eg. MaxStartups in OpenSSH).
  283.  
  284.  
  285. * Same as before, but without --rate-reset as we automatically detect when we have reached
  286. the maximum number of login attempts permitted per connection (eg. MaxAuthTries > 3 in OpenSSH).
  287. (a) Do not report wrong passwords.
  288. (b) Do not report everytime the server shuts down the TCP connection, reconnect and
  289. retry last password.
  290. --------- (a) (b)
  291. ssh_login ... -x ignore:mesg='Authentication failed.' -x ignore,reset,retry:mesg='No existing session'
  292.  
  293. }}}
  294. {{{ Telnet
  295.  
  296. * Brute-force authentication.
  297. (a) Enter login after first prompt is detected, enter password after second prompt.
  298. (b) The regex to detect the login and password prompts.
  299. (c) Reconnect when we get no login prompt back (max number of tries reached or successful login).
  300. ------------ (a)
  301. telnet_login host=10.0.0.1 inputs='FILE0\nFILE1' 0=logins.txt 1=passwords.txt
  302. prompt_re='Username:|Password:' -x reset:egrep!='% Login failed!.+Username:'
  303. (b) (c)
  304.  
  305. NB. If you get errors like "telnet connection closed", this is because they occur
  306. at TCP connect time, so try decreasing the number of threads, the server may
  307. be enforcing a maximum number of concurrent connections.
  308.  
  309. }}}
  310. {{{ SMTP
  311.  
  312. * Enumerate valid users using the VRFY command.
  313. (a) Do not report invalid recipients.
  314. (b) Do not report when the server shuts us down with "421 too many errors",
  315. reconnect and resume testing.
  316. --------- (a)
  317. smtp_vrfy host=10.0.0.1 user=FILE0 0=logins.txt -x ignore:fgrep='User unknown in local
  318. recipient table' -x ignore,reset,retry:code=421
  319. (b)
  320.  
  321. * Use the RCPT TO command in case the VRFY command was disabled.
  322. ---------
  323. smtp_rcpt host=10.0.0.1 user=FILE0@localhost 0=logins.txt helo='ehlo mx.fb.com' mail_from=root
  324.  
  325.  
  326. * Brute-force authentication.
  327. (a) Send a fake hostname (by default the real hostname is sent)
  328. ------------ (a)
  329. smtp_login host=10.0.0.1 helo='ehlo its.me.com' [email protected] password=FILE1 0=logins.txt 1=passwords.txt
  330.  
  331. }}}
  332. {{{ HTTP
  333.  
  334. * Find hidden Web resources.
  335. (a) Use a specific header.
  336. (b) Follow redirects.
  337. (c) Do not report 404 errors.
  338. (d) Retry on 500 errors.
  339. --------- (a)
  340. http_fuzz url=http://localhost/FILE0 0=words.txt header='Cookie: SESSID=A2FD8B2DA4'
  341. follow=1 -x ignore:code=404 -x ignore,retry:code=500
  342. (b) (c) (d)
  343.  
  344. NB. You may be able to go 10 times faster using webef (http://www.hsc.fr/ressources/outils/webef/).
  345. It is the fastest HTTP brute-forcer I know, yet at the moment it still lacks useful features
  346. that will prevent you from performing the following attacks.
  347.  
  348. * Brute-force phpMyAdmin logon.
  349. (a) Use POST requests.
  350. (b) Follow redirects using cookies sent by server.
  351. (c) Ignore failed authentications.
  352. --------- (a) (b) (b)
  353. http_fuzz url=http://10.0.0.1/phpmyadmin/index.php method=POST follow=1 accept_cookie=1
  354. body='pma_username=root&pma_password=FILE0&server=1&lang=en' 0=passwords.txt
  355. -x ignore:fgrep='Cannot log in to the MySQL server'
  356. (c)
  357.  
  358. * Scan subnet for directory listings.
  359. (a) Ignore not matching reponses.
  360. (b) Save matching responses into directory.
  361. ---------
  362. http_fuzz url=http://NET0/FILE1 0=10.0.0.0/24 1=dirs.txt -x ignore:fgrep!='Index of'
  363. -l /tmp/directory_listings (a)
  364. (b)
  365.  
  366. * Brute-force Basic authentication.
  367. (a) Single mode (login == password).
  368. (b) Do not report failed login attempts.
  369. ---------
  370. http_fuzz url=http://10.0.0.1/manager/html user_pass=FILE0:FILE0 0=logins.txt -x ignore:code=401
  371. (a) (b)
  372.  
  373. * Find hidden virtual hosts.
  374. (a) Read template from file.
  375. (b) Fuzz both the Host and User-Agent headers.
  376. ---------
  377. echo -e 'Host: FILE0\nUser-Agent: FILE1' > headers.txt
  378. http_fuzz url=http://10.0.0.1/ [email protected] 0=vhosts.txt 1=agents.txt
  379. (a) (b)
  380.  
  381. * Brute-force logon using GET requests.
  382. (a) Encode everything surrounded by the two tags _@@_ in hexadecimal.
  383. (b) Ignore HTTP 200 responses with a content size (header+body) within given range
  384. and that also contain the given string.
  385. (c) Use a different delimiter string because the comma cannot be escaped.
  386. --------- (a) (a)
  387. http_fuzz url='http://localhost/login?username=admin&password=_@@_FILE0_@@_' -e _@@_:hex
  388. 0=words.txt -x ignore:'code=200|size=1500-|fgrep=Welcome, unauthenticated user' -X'|'
  389. (b) (c)
  390.  
  391. * Test the OPTIONS method against a list of URLs.
  392. (a) Ignore URLs that only allow the HEAD and GET methods.
  393. (b) Header end of line is '\r\n'.
  394. (c) Use a different delimiter string because the comma cannot be escaped.
  395. ---------
  396. http_fuzz url=FILE0 0=urls.txt method=OPTIONS -x ignore:egrep='^Allow: HEAD, GET\r$' -X '|'
  397. (a) (b) (c)
  398. }}}
  399. {{{ LDAP
  400.  
  401. * Brute-force authentication.
  402. (a) Do not report wrong passwords.
  403. (b) Talk SSL/TLS to port 636.
  404. ---------
  405. ldap_login host=10.0.0.1 bindn='cn=FILE0,dc=example,dc=com' 0=logins.txt bindpw=FILE1 1=passwords.txt
  406. -x ignore:mesg='ldap_bind: Invalid credentials (49)' ssl=1 port=636
  407. (a) (b)
  408. }}}
  409. {{{ SMB
  410.  
  411. * Brute-force authentication.
  412. (a) Do not report wrong passwords.
  413. (b) Reconnect when a valid password is found (need to logoff before testing other passwords).
  414. ---------
  415. smb_login host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt
  416. -x ignore:fgrep=STATUS_LOGON_FAILURE -x reset:code=0
  417. (a) (b)
  418.  
  419. NB. If you suddenly get STATUS_ACCOUNT_LOCKED_OUT errors for an account
  420. although it is not the first password you test on this account, then you must
  421. have locked it.
  422.  
  423.  
  424. * Pass-the-hash.
  425. (a) Test a list of hosts.
  426. (b) Test every user (each line := login:rid:LM hash:NT hash).
  427. --------- (a) (b)
  428. smb_login host=FILE0 0=hosts.txt user=COMBO10 password_hash=COMBO12:COMBO13 1=pwdump.txt -x ...
  429.  
  430. }}}
  431. {{{ MSSQL
  432.  
  433. * Brute-force authentication.
  434. -----------
  435. mssql_login host=10.0.0.1 user=sa password=FILE0 0=passwords.txt -x ignore:fgrep='Login failed for user'
  436.  
  437. }}}
  438. {{{ Oracle
  439. Beware, by default in Oracle, accounts are permanently locked out after 10 wrong passwords,
  440. except for the SYS account.
  441.  
  442. * Brute-force authentication.
  443. ------------
  444. oracle_login host=10.0.0.1 user=SYS password=FILE0 0=passwords.txt sid=ORCL -x ignore:code=ORA-01017
  445.  
  446. NB0. With Oracle 10g XE (Express Edition), you do not need to pass a SID.
  447.  
  448. NB1. If you get ORA-12516 errors, it may be because you reached the limit of
  449. concurrent connections or db processes, try using "--rate-limit 0.5 -t 2" to be
  450. more polite. Also you can run "alter system set processes=150 scope=spfile;"
  451. and restart your database to get rid of this.
  452.  
  453.  
  454. * Brute-force SID.
  455. ------------
  456. oracle_login host=10.0.0.1 sid=FILE0 0=sids.txt -x ignore:code=ORA-12505
  457.  
  458. NB. Against Oracle9, it may crash (Segmentation fault) as soon as a valid SID is
  459. found (cx_Oracle bug). Sometimes, the SID gets printed out before the crash,
  460. so try running the same command again if it did not.
  461.  
  462. }}}
  463. {{{ MySQL
  464.  
  465. * Brute-force authentication.
  466. -----------
  467. mysql_login host=10.0.0.1 user=FILE0 password=FILE0 0=logins.txt -x ignore:fgrep='Access denied for user'
  468.  
  469. }}}
  470. {{{ PostgresSQL
  471.  
  472. * Brute-force authentication.
  473. -----------
  474. pgsql_login host=10.0.0.1 user=postgres password=FILE0 0=passwords.txt
  475. -x ignore:fgrep='password authentication failed for user'
  476.  
  477. }}}
  478. {{{ VNC
  479. Some VNC servers have built-in anti-bruteforce functionnality that temporarily
  480. blacklists the attacker IP address after too many wrong passwords.
  481. - RealVNC-4.1.3 or TightVNC-1.3.10 for example, allow 5 failed attempts and
  482. then enforce a 10 second delay. For each subsequent failed attempt that
  483. delay is doubled.
  484. - RealVNC-3.3.7 or UltraVNC allow 6 failed attempts and then enforce a 10
  485. second delay between each following attempt.
  486.  
  487. * Brute-force authentication.
  488. (a) No need to use more than one thread.
  489. (b) Keep retrying the same password when we are blacklisted by the server.
  490. (c) Exit execution as soon as a valid password is found.
  491. --------- (a)
  492. vnc_login host=10.0.0.1 password=FILE0 0=passwords.txt --threads 1
  493. -x retry:fgrep!='Authentication failure' --max-retries -1 -x quit:code=0
  494. (b) (b) (c)
  495. }}}
  496. {{{ Unzip
  497.  
  498. * Brute-force the ZIP file password.
  499. ----------
  500. unzip_pass zipfile=path/to/file.zip password=FILE0 0=passwords.txt -x ignore:code!=0
  501.  
  502. }}}
  503. {{{ DNS
  504.  
  505. * Forward lookup subdomains.
  506. (a) Ignore NXDOMAIN responses (rcode 3).
  507. -----------
  508. dns_forward domain=FILE0.google.com 0=names.txt -x ignore:code=3
  509. (a)
  510. * Forward lookup domain with all possible TLDs.
  511. -----------
  512. dns_forward domain=google.MOD0 0=TLD -x ignore:code=3
  513.  
  514. * Foward lookup SRV records.
  515. -----------
  516. dns_forward domain=MOD0.microsoft.com 0=SRV qtype=SRV -x ignore:code=3
  517.  
  518. * Reverse lookup several subnets.
  519. (a) Ignore names that do not contain 'google.com'.
  520. (b) Ignore generic PTR records.
  521. -----------
  522. dns_reverse host=NET0 0=216.239.32.0-216.239.47.255,8.8.8.0/24 -x ignore:code=3 -x ignore:fgrep!=google.com -x ignore:fgrep=216-239-
  523. (a) (b)
  524. }}}
  525. {{{ SNMP
  526.  
  527. * SNMPv1/2 : Find valid community names.
  528. ----------
  529. snmp_login host=10.0.0.1 community=FILE0 1=names.txt -x ignore:mesg='No SNMP response received before timeout'
  530.  
  531.  
  532. * SNMPv3 : Find valid usernames.
  533. ----------
  534. snmp_login host=10.0.0.1 version=3 user=FILE0 0=logins.txt -x ignore:mesg=unknownUserName
  535.  
  536.  
  537. * SNMPv3 : Find valid passwords.
  538. ----------
  539. snmp_login host=10.0.0.1 version=3 user=myuser auth_key=FILE0 0=passwords.txt -x ignore:mesg=wrongDigest
  540.  
  541. NB0. If you get "notInTimeWindow" error messages, increase the retries option.
  542. NB1. SNMPv3 requires passphrases to be at least 8 characters long.
  543.  
  544. }}}
  545.  
  546. CHANGELOG
  547. ---------
  548.  
  549. * v0.3 2011/12/16
  550. - minor bugs fixed in http_fuzz
  551. - option -e better implemented
  552. - better warnings about missing dependencies
  553.  
  554. * v0.2 2011/12/01
  555. - new smtp_login module
  556. - several bugs fixed
  557.  
  558. * v0.1 2011/11/25 : Public release
  559.  
  560.  
  561. TODO
  562. ----
  563. * SSL support for SMTP, MySQL, ... (use socat in the meantime)
  564. * new option -e ns like in Medusa (not likely to be implemented due to design)
  565. * replace PyDNS|paramiko|IPy with a better module (scapy|libssh2|... ?)
  566. '''
  567.  
  568. # }}}
  569.  
  570. # imports and logging {{{
  571. import logging
  572. formatter = logging.Formatter('%(asctime)s %(name)-7s %(levelname)7s - %(message)s', datefmt='%H:%M:%S')
  573. handler = logging.StreamHandler()
  574. handler.setFormatter(formatter)
  575. logger = logging.getLogger('patator')
  576. logger.setLevel(logging.INFO)
  577. logger.addHandler(handler)
  578.  
  579. import re
  580. from time import sleep, time
  581. from Queue import Queue, Empty, Full
  582. from threading import Thread, active_count
  583. from select import select
  584. from sys import stdin, exc_info, exit
  585. import os
  586. from time import localtime, strftime, sleep
  587. from itertools import product, chain, islice
  588. from string import ascii_lowercase
  589. from binascii import hexlify
  590. from base64 import b64encode
  591. from datetime import timedelta, datetime
  592. from struct import unpack
  593. import socket
  594. import subprocess
  595. import hashlib
  596.  
  597. warnings = []
  598. try:
  599. from IPy import IP
  600. has_ipy = True
  601. except ImportError:
  602. has_ipy = False
  603.  
  604. # imports }}}
  605.  
  606. # utils {{{
  607. def which(program):
  608. def is_exe(fpath):
  609. return os.path.exists(fpath) and os.access(fpath, os.X_OK)
  610.  
  611. fpath, fname = os.path.split(program)
  612. if fpath:
  613. if is_exe(program):
  614. return program
  615. else:
  616. for path in os.environ["PATH"].split(os.pathsep):
  617. exe_file = os.path.join(path, program)
  618. if is_exe(exe_file):
  619. return exe_file
  620.  
  621. return None
  622.  
  623. def create_dir(top_path):
  624. top_path = os.path.abspath(top_path)
  625. if os.path.isdir(top_path):
  626. files = os.listdir(top_path)
  627. if files:
  628. if raw_input("Directory '%s' is not empty, do you want to wipe it ? [Y/n]: " % top_path) == 'n':
  629. exit(0)
  630. for root, dirs, files in os.walk(top_path):
  631. if dirs:
  632. print("Directory '%s' contains sub-directories, safely aborting..." % root)
  633. exit(0)
  634. for f in files:
  635. os.unlink(os.path.join(root, f))
  636. break
  637. else:
  638. os.mkdir(top_path)
  639. return top_path
  640.  
  641. def create_time_dir(top_path, desc):
  642. now = localtime()
  643. date, time = strftime('%Y-%m-%d', now), strftime('%H%M%S', now)
  644. top_path = os.path.abspath(top_path)
  645. date_path = os.path.join(top_path, date)
  646. time_path = os.path.join(top_path, date, time + '_' + desc)
  647.  
  648. if not os.path.isdir(top_path):
  649. os.makedirs(top_path)
  650. if not os.path.isdir(date_path):
  651. os.mkdir(date_path)
  652. if not os.path.isdir(time_path):
  653. os.mkdir(time_path)
  654.  
  655. return time_path
  656.  
  657. def pprint_seconds(seconds, fmt):
  658. return fmt % reduce(lambda x,y: divmod(x[0], y) + x[1:], [(seconds,),60,60])
  659.  
  660. def md5hex(plain):
  661. return hashlib.md5(plain).hexdigest()
  662.  
  663. def sha1hex(plain):
  664. return hashlib.sha1(plain).hexdigest()
  665.  
  666. # }}}
  667.  
  668. # Controller {{{
  669. class Controller:
  670. actions = {}
  671. paused = False
  672. start_time = 0
  673. total_size = 1
  674. log_dir = None
  675. thread_report = []
  676. thread_progress = []
  677.  
  678. payload = {}
  679. iter_keys = {}
  680. enc_keys = []
  681.  
  682. builtin_actions = (
  683. ('ignore', 'do not report'),
  684. ('retry', 'try payload again'),
  685. ('quit', 'terminate execution now'),
  686. )
  687.  
  688. available_encodings = {
  689. 'hex': (hexlify, 'encode in hexadecimal'),
  690. 'b64': (b64encode, 'encode in base64'),
  691. 'md5': (md5hex, 'hash in md5'),
  692. 'sha1': (sha1hex, 'hash in sha1'),
  693. }
  694.  
  695. def expand_key(self, arg):
  696. yield arg.split('=', 1)
  697.  
  698. def find_file_keys(self, value):
  699. return map(int, re.findall(r'FILE(\d)', value))
  700.  
  701. def find_net_keys(self, value):
  702. return map(int, re.findall(r'NET(\d)', value))
  703.  
  704. def find_combo_keys(self, value):
  705. return [map(int, t) for t in re.findall(r'COMBO(\d)(\d)', value)]
  706.  
  707. def find_module_keys(self, value):
  708. return map(int, re.findall(r'MOD(\d)', value))
  709.  
  710. def usage_parser(self, name):
  711. from optparse import OptionParser
  712. from optparse import OptionGroup
  713.  
  714. usage_hints = self.module.usage_hints
  715.  
  716. available_actions = self.builtin_actions + self.module.available_actions
  717. available_conditions = self.module.Response.available_conditions
  718.  
  719. parser = OptionParser()
  720. usage = '''
  721. %s''' % '\n'.join(usage_hints)
  722.  
  723. usage += '''
  724.  
  725. Module options:
  726. %s
  727.  
  728. * Allowed format in ()
  729. * Allowed values in [] with the default value always listed first
  730. ''' % ('\n'.join(' %-14s: %s' % (k, v) for k, v in self.module.available_options))
  731.  
  732. usage += '''
  733. Syntax:
  734. -x actions:conditions
  735.  
  736. actions := action[,action]*
  737. action := "%s"
  738. conditions := condition=value[,condition=value]*
  739. condition := "%s"
  740. ''' % ('" | "'.join(k for k, v in available_actions),
  741. '" | "'.join(k for k, v in available_conditions))
  742.  
  743. usage += '''
  744. %s
  745.  
  746. %s
  747. ''' % ('\n'.join(' %-12s: %s' % (k, v) for k, v in available_actions),
  748. '\n'.join(' %-12s: %s' % (k, v) for k, v in available_conditions))
  749.  
  750. usage += '''
  751. For example, to ignore all redirects to the home page:
  752. ... -x ignore:code=302,fgrep='Location: /home.html'
  753.  
  754. -e tag:encoding
  755.  
  756. tag := any unique string (eg. T@G or _@@_ or ...)
  757. encoding := "%s"
  758.  
  759. %s''' % ('" | "'.join(k for k in self.available_encodings),
  760. '\n'.join(' %-12s: %s' % (k, v) for k, (f, v) in self.available_encodings.iteritems()))
  761.  
  762. usage += '''
  763.  
  764. For example, to encode every password in base64:
  765. ... host=10.0.0.1 user=admin password=_@@_FILE0_@@_ -e _@@_:b64
  766. '''
  767.  
  768.  
  769. parser.usage = usage.replace('%prog', name)
  770.  
  771. exe_grp = OptionGroup(parser, 'Execution')
  772. exe_grp.add_option('-x', dest='actions', action='append', default=[], metavar='arg', help='actions and conditions, see Syntax above')
  773. exe_grp.add_option('--start', dest='start', type='int', default=0, metavar='N', help='start from offset N in the wordlist product')
  774. exe_grp.add_option('--stop', dest='stop', type='int', default=None, metavar='N', help='stop at offset N')
  775. exe_grp.add_option('--resume', dest='resume', metavar='r1[,rN]*', help='resume previous run')
  776. exe_grp.add_option('-e', dest='encodings', action='append', default=[], metavar='arg', help='encode everything between two tags, see Syntax above')
  777. exe_grp.add_option('-C', dest='combo_delim', default=':', metavar='str', help="delimiter string in combo files (default is ':')")
  778. exe_grp.add_option('-X', dest='condition_delim', default=',', metavar='str', help="delimiter string in conditions (default is ',')")
  779.  
  780. opt_grp = OptionGroup(parser, 'Optimization')
  781. opt_grp.add_option('--rate-limit', dest='rate_limit', type='float', default=0, metavar='N', help='wait N seconds between tests (default is 0)')
  782. opt_grp.add_option('--rate-reset', dest='rate_reset', type='int', default=0, metavar='N', help='reset module every N tests (default is 0: never reset)')
  783. opt_grp.add_option('--failure-delay', dest='failure_delay', type='float', default=0.5, metavar='N', help='wait N seconds after a failure (default is 0.5)')
  784. opt_grp.add_option('--max-retries', dest='max_retries', type='int', default=5, metavar='N', help='skip payload after N failures (default is 5) (-1 for unlimited)')
  785. opt_grp.add_option('-t', '--threads', dest='num_threads', type='int', default=10, metavar='N', help='number of threads (default is 10)')
  786.  
  787. log_grp = OptionGroup(parser, 'Logging')
  788. log_grp.add_option('-l', dest='log_dir', metavar='DIR', help="save output and response data into DIR ")
  789. log_grp.add_option('-L', dest='auto_log', metavar='SFX', help="automatically save into DIR/yyyy-mm-dd/hh:mm:ss_SFX (DIR defaults to '/tmp/patator')")
  790.  
  791. dbg_grp = OptionGroup(parser, 'Debugging')
  792. dbg_grp.add_option('-d', '--debug', dest='debug', action='store_true', default=False, help='enable debug messages')
  793.  
  794. parser.option_groups.extend([exe_grp, opt_grp, log_grp, dbg_grp])
  795.  
  796. return parser
  797.  
  798. def parse_usage(self, argv):
  799. parser = self.usage_parser(argv[0])
  800. opts, args = parser.parse_args(argv[1:])
  801.  
  802. if opts.debug:
  803. logger.setLevel(logging.DEBUG)
  804.  
  805. if not len(args) > 0:
  806. parser.print_help()
  807. print('\nERROR: wrong usage. Please read the README inside for more information.')
  808. exit(2)
  809.  
  810. return opts, args
  811.  
  812. def __init__(self, module, argv):
  813. self.module = module
  814. opts, args = self.parse_usage(argv)
  815.  
  816. self.combo_delim = opts.combo_delim
  817. self.condition_delim = opts.condition_delim
  818. self.rate_reset = opts.rate_reset
  819. self.rate_limit = opts.rate_limit
  820. self.failure_delay = opts.failure_delay
  821. self.max_retries = opts.max_retries
  822. self.num_threads = opts.num_threads
  823. self.start, self.stop, self.resume = opts.start, opts.stop, opts.resume
  824.  
  825. wlists = {}
  826. kargs = []
  827. for arg in args: # ('host=NET0', '0=10.0.0.0/24', 'user=COMBO10', 'password=COMBO11', '1=combos.txt', 'domain=MOD2', '2=TLD')
  828. for k, v in self.expand_key(arg):
  829. logger.debug('k: %s, v: %s' % (k, v))
  830.  
  831. if k.isdigit():
  832. wlists[k] = v
  833.  
  834. else:
  835. if v.startswith('@'):
  836. p = os.path.expanduser(v[1:])
  837. v = open(p).read()
  838. kargs.append((k, v))
  839.  
  840. iter_vals = [v for k, v in sorted(wlists.iteritems())]
  841. logger.debug('iter_vals: %s' % iter_vals) # ['10.0.0.0/24', 'combos.txt', 'TLD']
  842. logger.debug('kargs: %s' % kargs) # [('host', 'NET0'), ('user', 'COMBO10'), ('password', 'COMBO11'), ('domain', 'MOD2')]
  843.  
  844. for k, v in kargs:
  845.  
  846. for e in opts.encodings:
  847. meta, enc = e.split(':')
  848. if re.search(r'{0}.+?{0}'.format(meta), v):
  849. self.enc_keys.append((k, meta, self.available_encodings[enc][0]))
  850.  
  851. for i in self.find_file_keys(v):
  852. if i not in self.iter_keys:
  853. self.iter_keys[i] = ('FILE', iter_vals[i], [])
  854. self.iter_keys[i][2].append(k)
  855.  
  856. else:
  857. for i in self.find_net_keys(v):
  858. if i not in self.iter_keys:
  859. self.iter_keys[i] = ('NET', iter_vals[i], [])
  860. self.iter_keys[i][2].append(k)
  861.  
  862. if not has_ipy:
  863. logger.warn('IPy (https://github.com/haypo/python-ipy) is required for using NETx keywords.')
  864. logger.warn('Please read the README inside for more information.')
  865. exit(3)
  866.  
  867. else:
  868. for i, j in self.find_combo_keys(v):
  869. if i not in self.iter_keys:
  870. self.iter_keys[i] = ('COMBO', iter_vals[i], [])
  871. self.iter_keys[i][2].append((j, k))
  872.  
  873. else:
  874. for i in self.find_module_keys(v):
  875. if i not in self.iter_keys:
  876. self.iter_keys[i] = ('MOD', iter_vals[i], [])
  877. self.iter_keys[i][2].append(k)
  878.  
  879. else:
  880. self.payload[k] = v
  881.  
  882. # { 0: ('NET', '10.0.0.0/24', ['host']), 1: ('COMBO', 'combos.txt', [(0, 'user'), (1, 'password')]), 2: ('MOD', 'TLD', ['domain'])
  883. logger.debug('iter_keys: %s' % self.iter_keys)
  884. logger.debug('enc_keys: %s' % self.enc_keys) # [('password', 'ENC', hexlify), ('header', 'B64', b64encode), ...
  885. logger.debug('payload: %s' % self.payload)
  886.  
  887. for k, _ in self.builtin_actions:
  888. self.actions[k] = []
  889.  
  890. self.module_actions = [k for k, _ in self.module.available_actions]
  891. for k in self.module_actions:
  892. self.actions[k] = []
  893.  
  894. for x in opts.actions:
  895. self.update_actions(x)
  896.  
  897. logger.debug('actions: %s' % self.actions)
  898.  
  899. if opts.auto_log:
  900. self.log_dir = create_time_dir(opts.log_dir or '/tmp/patator', opts.auto_log)
  901. elif opts.log_dir:
  902. self.log_dir = create_dir(opts.log_dir)
  903.  
  904. if self.log_dir:
  905. log_file = os.path.join(self.log_dir, 'RUNTIME.log')
  906. with open(log_file, 'w') as f:
  907. f.write('$ %s\n' % ' '.join(argv))
  908.  
  909. handler = logging.FileHandler(log_file)
  910. handler.setFormatter(formatter)
  911. logging.getLogger('patator').addHandler(handler)
  912.  
  913. def update_actions(self, arg):
  914. actions, conditions = arg.split(':', 1)
  915.  
  916. for action in actions.split(','):
  917. conds = conditions.split(self.condition_delim)
  918. new_cond = []
  919.  
  920. for cond in conds:
  921. key, val = cond.split('=', 1)
  922. new_cond.append((key, val))
  923.  
  924. self.actions[action].append(new_cond)
  925.  
  926. def lookup_actions(self, resp):
  927. actions = []
  928. for action, conditions in self.actions.iteritems():
  929. for condition in conditions:
  930. for key, val in condition:
  931. if key[-1] == '!':
  932. if resp.match(key[:-1], val):
  933. break
  934. else:
  935. if not resp.match(key, val):
  936. break
  937. else:
  938. actions.append(action)
  939. return actions
  940.  
  941. def fire(self):
  942. logger.info('Starting Patator v%s (%s) at %s'
  943. % (__version__, __git__, strftime('%Y-%m-%d %H:%M %Z', localtime())))
  944.  
  945. try:
  946. self.start_threads()
  947. self.monitor_progress()
  948. except SystemExit:
  949. pass
  950. except KeyboardInterrupt:
  951. print
  952. except:
  953. logger.exception(exc_info()[1])
  954.  
  955. hits_count = sum(p.hits_count for p in self.thread_progress)
  956. done_count = sum(p.done_count for p in self.thread_progress)
  957. fail_count = sum(p.fail_count for p in self.thread_progress)
  958.  
  959. total_time = time() - self.start_time
  960. speed_avg = done_count / total_time
  961.  
  962. self.show_final()
  963.  
  964. logger.info('Hits/Done/Size/Fail: %d/%d/%d/%d, Avg: %d r/s, Time: %s' % (hits_count,
  965. done_count, self.total_size, fail_count, speed_avg, pprint_seconds(total_time, '%dh %dm %ds')))
  966.  
  967. if self.total_size != done_count:
  968. resume = []
  969. for i, p in enumerate(self.thread_progress):
  970. c = p.done_count
  971. if self.resume:
  972. if i < len(self.resume):
  973. c += self.resume[i]
  974. resume.append(str(c))
  975.  
  976. logger.info('To resume execution, pass --resume %s' % ','.join(resume))
  977.  
  978. def push_final(self, resp): pass
  979. def show_final(self): pass
  980.  
  981. def start_threads(self):
  982. gqueues = [Queue(maxsize=10000) for i in range(self.num_threads)]
  983.  
  984. # producer
  985. t = Thread(target=self.produce, args=(gqueues,))
  986. t.daemon = True
  987. t.start()
  988.  
  989. class Progress:
  990. def __init__(self):
  991. self.current = ''
  992. self.done_count = 0
  993. self.hits_count = 0
  994. self.fail_count = 0
  995. self.seconds = [1]*25 # avoid division by zero early bug condition
  996.  
  997. # consumers
  998. for num in range(self.num_threads):
  999. pqueue = Queue()
  1000. t = Thread(target=self.consume, args=(gqueues[num], pqueue))
  1001. t.daemon = True
  1002. t.start()
  1003. self.thread_report.append(pqueue)
  1004. self.thread_progress.append(Progress())
  1005.  
  1006. def produce(self, queues):
  1007.  
  1008. iterables = []
  1009. for t, v, _ in self.iter_keys.itervalues():
  1010.  
  1011. if t in ('FILE', 'COMBO'):
  1012. #iterable, size = self.builtin_keywords[t](v)
  1013. files = map(os.path.expanduser, v.split(','))
  1014. size = sum(sum(1 for _ in open(f)) for f in files)
  1015. iterable = chain(*map(open, files))
  1016.  
  1017. elif t == 'NET':
  1018. subnets = [IP(n, make_net=True) for n in v.split(',')]
  1019. size = sum(len(s) for s in subnets)
  1020. iterable = chain(*subnets)
  1021.  
  1022. elif t == 'MOD':
  1023. iterable, size = self.module.available_keys[v]()
  1024.  
  1025. else:
  1026. raise NotImplementedError("Incorrect keyword '%s'" % t)
  1027.  
  1028. self.total_size *= size
  1029. iterables.append(iterable)
  1030.  
  1031. if self.stop:
  1032. self.total_size = self.stop - self.start
  1033. else:
  1034. self.total_size -= self.start
  1035.  
  1036. if self.resume:
  1037. self.resume = map(int, self.resume.split(','))
  1038. self.total_size -= sum(self.resume)
  1039.  
  1040. logger.info('')
  1041. logger.info('%-15s | %-25s \t | %5s | %s' % ('code & size', 'candidate', 'num', 'mesg'))
  1042. logger.info('-' * 63)
  1043.  
  1044. self.start_time = time()
  1045. count = 0
  1046. for pp in islice(product(*iterables), self.start, self.stop):
  1047.  
  1048. cid = count % self.num_threads
  1049. prod = map(lambda s: str(s).strip('\r\n'), pp)
  1050.  
  1051. if self.resume:
  1052. idx = count % len(self.resume)
  1053. off = self.resume[idx]
  1054.  
  1055. if count < off * len(self.resume):
  1056. logger.debug('Skipping %d %s, resume[%d]: %s' % (count, ':'.join(prod), idx, self.resume[idx]))
  1057. count += 1
  1058. continue
  1059.  
  1060. queues[cid].put(prod)
  1061. count += 1
  1062.  
  1063. for q in queues:
  1064. q.put(None)
  1065.  
  1066. def consume(self, gqueue, pqueue):
  1067. module = self.module()
  1068. rate_count = 0
  1069.  
  1070. while True:
  1071. prod = gqueue.get()
  1072. if not prod: return
  1073.  
  1074. payload = self.payload.copy()
  1075.  
  1076. for i, (t, _, keys) in self.iter_keys.iteritems():
  1077. if t == 'FILE':
  1078. for k in keys:
  1079. payload[k] = payload[k].replace('FILE%d' % i, prod[i])
  1080. elif t == 'NET':
  1081. for k in keys:
  1082. payload[k] = payload[k].replace('NET%d' % i, prod[i])
  1083. elif t == 'COMBO':
  1084. for j, k in keys:
  1085. payload[k] = payload[k].replace('COMBO%d%d' % (i, j), prod[i].split(self.combo_delim)[j])
  1086. elif t == 'MOD':
  1087. for k in keys:
  1088. payload[k] = payload[k].replace('MOD%d' %i, prod[i])
  1089.  
  1090. for k, m, e in self.enc_keys:
  1091. payload[k] = re.sub(r'{0}(.+?){0}'.format(m), lambda m: e(m.group(1)), payload[k])
  1092.  
  1093. pp_prod = ':'.join(prod)
  1094. logger.debug('pp_prod: %s' % pp_prod)
  1095.  
  1096. num_try = 0
  1097. start_time = time()
  1098. while num_try < self.max_retries or self.max_retries < 0:
  1099. num_try += 1
  1100.  
  1101. while self.paused:
  1102. sleep(1)
  1103.  
  1104. if self.rate_reset > 0:
  1105. if rate_count >= self.rate_reset:
  1106. logger.debug('Reset module')
  1107. module = self.module()
  1108. rate_count = 0
  1109.  
  1110. if self.rate_limit:
  1111. sleep(self.rate_limit)
  1112.  
  1113. logger.debug('payload: %s' % payload)
  1114.  
  1115. try:
  1116. rate_count += 1
  1117. resp = module.execute(**payload)
  1118.  
  1119. except:
  1120. e_type, e_value, _ = exc_info()
  1121. resp = '%s, %s' % (e_type, e_value.args)
  1122. logger.debug('except: %s' % resp)
  1123. module = self.module()
  1124. rate_count = 0
  1125. sleep(self.failure_delay)
  1126. continue
  1127.  
  1128. actions = self.lookup_actions(resp)
  1129. pqueue.put_nowait((actions, pp_prod, resp, time() - start_time))
  1130.  
  1131. for a in self.module_actions:
  1132. if a in actions:
  1133. getattr(module, a)(**payload)
  1134.  
  1135. if 'retry' in actions:
  1136. logger.debug('Retry %d/%d: %s' % (num_try, self.max_retries, resp))
  1137. sleep(self.failure_delay)
  1138. continue
  1139.  
  1140. break
  1141.  
  1142. else:
  1143. pqueue.put_nowait((['fail'], pp_prod, resp, time() - start_time))
  1144.  
  1145. def monitor_progress(self):
  1146. while active_count() > 1:
  1147. self.report_progress()
  1148. self.monitor_interaction()
  1149.  
  1150. self.report_progress()
  1151.  
  1152. def report_progress(self):
  1153. for i, pq in enumerate(self.thread_report):
  1154. while True:
  1155.  
  1156. try:
  1157. actions, current, resp, seconds = pq.get_nowait()
  1158. logger.debug('actions: %s' % actions)
  1159.  
  1160. except Empty:
  1161. break
  1162.  
  1163. p = self.thread_progress[i]
  1164. offset = (self.start + p.done_count * self.num_threads) + i + 1
  1165. p.current = current
  1166. p.seconds[p.done_count % len(p.seconds)] = seconds
  1167.  
  1168. if 'fail' in actions:
  1169. p.fail_count += 1
  1170. p.done_count += 1
  1171. logger.warn('%-15s | %-25s \t | %5d | %s' % ('xxx', current, offset, resp))
  1172. continue
  1173.  
  1174. if 'ignore' not in actions:
  1175. p.hits_count += 1
  1176. logger.info('%-15s | %-25s \t | %5d | %s' % (resp.compact(), current, offset, resp))
  1177.  
  1178. if self.log_dir:
  1179. filename = '%d_%s' % (offset, resp.compact().replace(' ', '_'))
  1180. with open('%s/%s.txt' % (self.log_dir, filename), 'w') as f:
  1181. f.write(resp.dump())
  1182.  
  1183. self.push_final(resp)
  1184.  
  1185. if 'retry' not in actions:
  1186. p.done_count += 1
  1187.  
  1188. if 'quit' in actions:
  1189. logger.info('Quitting (user match condition)')
  1190. raise SystemExit
  1191.  
  1192.  
  1193. def monitor_interaction(self):
  1194.  
  1195. i, _, _ = select([stdin], [], [], .1)
  1196. if not i: return
  1197. command = stdin.readline().strip()
  1198.  
  1199. if command == 'h':
  1200. logger.info('''Available commands:
  1201. h show help
  1202. <Enter> show progress
  1203. d/D increase/decrease debug level
  1204. p pause progress
  1205. f show verbose progress
  1206. x arg add monitor condition
  1207. a show all active conditions
  1208. q terminate execution now
  1209. ''')
  1210.  
  1211. elif command == 'q':
  1212. raise KeyboardInterrupt
  1213.  
  1214. elif command == 'p':
  1215. self.paused = not self.paused
  1216. logger.info(self.paused and 'Paused' or 'Unpaused')
  1217.  
  1218. elif command == 'd':
  1219. logger.setLevel(logging.DEBUG)
  1220.  
  1221. elif command == 'D':
  1222. logger.setLevel(logging.INFO)
  1223.  
  1224. elif command == 'a':
  1225. logger.info(self.actions)
  1226.  
  1227. elif command.startswith('x'):
  1228. _, arg = command.split(' ', 1)
  1229. self.update_actions(arg)
  1230.  
  1231. else: # show progress
  1232. total_count = sum(p.done_count for p in self.thread_progress)
  1233. speed_avg = self.num_threads / (sum(sum(p.seconds) / len(p.seconds) for p in self.thread_progress) / self.num_threads)
  1234. remain_seconds = (self.total_size - total_count) / speed_avg
  1235. etc_time = datetime.now() + timedelta(seconds = remain_seconds)
  1236.  
  1237. logger.info('Progress: {0:>3}% ({1}/{2}) | Speed: {3:.0f} r/s | ETC: {4} ({5} remaining) {6}'.format(
  1238. total_count * 100/self.total_size,
  1239. total_count,
  1240. self.total_size,
  1241. speed_avg,
  1242. etc_time.strftime('%H:%M:%S'),
  1243. pprint_seconds(remain_seconds, '%02d:%02d:%02d'),
  1244. self.paused and '| Paused' or ''))
  1245.  
  1246. if command == 'f':
  1247. for i, p in enumerate(self.thread_progress):
  1248. logger.info(' #{0}: {1:>3}% ({2}/{3}) {4}'.format(
  1249. i,
  1250. p.done_count * 100/(self.total_size/self.num_threads),
  1251. p.done_count,
  1252. self.total_size/self.num_threads,
  1253. p.current))
  1254.  
  1255. # }}}
  1256.  
  1257. # Response_Base {{{
  1258. def match_size(size, val):
  1259. if '-' in val:
  1260. size_min, size_max = val.split('-')
  1261.  
  1262. if not size_min and not size_max:
  1263. raise ValueError, 'Invalid interval'
  1264.  
  1265. elif not size_min: # size == -N
  1266. return size <= int(size_max)
  1267.  
  1268. elif not size_max: # size == N-
  1269. return size >= int(size_min)
  1270.  
  1271. else:
  1272. size_min, size_max = int(size_min), int(size_max)
  1273. if size_min >= size_max:
  1274. raise ValueError, 'Invalid interval'
  1275.  
  1276. return size_min <= size <= size_max
  1277.  
  1278. else:
  1279. return size == int(val)
  1280.  
  1281. class Response_Base:
  1282.  
  1283. available_conditions = (
  1284. ('code', 'match status code'),
  1285. ('size', 'match size (N or N-M or N- or -N)'),
  1286. ('mesg', 'match message'),
  1287. ('fgrep', 'search for string'),
  1288. ('egrep', 'search for regex'),
  1289. )
  1290.  
  1291. def __init__(self, code, mesg, trace=''):
  1292. self.code, self.mesg = code, mesg
  1293. self.size = len(self.mesg)
  1294. self.trace = trace
  1295.  
  1296. def compact(self):
  1297. return '%s %s' % (self.code, self.size)
  1298.  
  1299. def __str__(self):
  1300. return self.mesg
  1301.  
  1302. def match(self, key, val):
  1303. return getattr(self, 'match_'+key)(val)
  1304.  
  1305. def match_code(self, val):
  1306. return val == str(self.code)
  1307.  
  1308. def match_size(self, val):
  1309. return match_size(self.size, val)
  1310.  
  1311. def match_mesg(self, val):
  1312. return val == self.mesg
  1313.  
  1314. def match_fgrep(self, val):
  1315. return val in str(self)
  1316.  
  1317. def match_egrep(self, val):
  1318. return re.search(val, str(self))
  1319.  
  1320. def dump(self):
  1321. return self.trace or str(self)
  1322.  
  1323. # }}}
  1324.  
  1325. # TCP_Cache {{{
  1326. class TCP_Cache:
  1327.  
  1328. available_actions = (
  1329. ('reset', 'close current connection in order to reconnect for next probe'),
  1330. )
  1331.  
  1332. available_options = (
  1333. ('persistent', 'use persistent connections [1|0]'),
  1334. )
  1335.  
  1336. cache_keys = ('host', 'port')
  1337.  
  1338. def __init__(self):
  1339. self.cache = {} # {'10.0.0.1:21': fp, ...}
  1340.  
  1341. def __del__(self):
  1342. for k in self.cache.keys():
  1343. self.del_tcp(k)
  1344.  
  1345. def get_key(self, **kwargs):
  1346. keys = []
  1347. for k in self.cache_keys:
  1348. if k in kwargs:
  1349. keys.append(kwargs[k])
  1350. return ':'.join(k for k in keys if k is not None), keys
  1351.  
  1352. def get_tcp(self, persistent, **kwargs):
  1353. k, z = self.get_key(**kwargs)
  1354. if k not in self.cache:
  1355.  
  1356. logger.debug('New connection: %s' % k)
  1357. fp, banner = self.new_tcp(*z)
  1358.  
  1359. if persistent == '1':
  1360. self.cache[k] = fp
  1361.  
  1362. else:
  1363. fp, banner = self.cache[k], ''
  1364.  
  1365. return fp, banner
  1366.  
  1367. def del_tcp(self, k):
  1368. if k in self.cache:
  1369. logger.debug('Delete connection: %s' % k)
  1370. fp = self.cache[k]
  1371. try: fp.close()
  1372. except: pass
  1373. del self.cache[k]
  1374.  
  1375. def reset(self, **kwargs):
  1376. k, _ = self.get_key(**kwargs)
  1377. logger.debug('Reset connection: %s' % k)
  1378. self.del_tcp(k)
  1379.  
  1380. # }}}
  1381.  
  1382. # FTP {{{
  1383. from ftplib import FTP, Error as FTP_Error
  1384. class FTP_login(TCP_Cache):
  1385. '''Brute-force FTP authentication'''
  1386.  
  1387. usage_hints = (
  1388. """%prog host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt"""
  1389. """ -x ignore:mesg='Login incorrect.' -x ignore,reset,retry:code=500 -x reset:fgrep='Login successful'""",
  1390. )
  1391.  
  1392. available_options = (
  1393. ('host', 'hostnames or subnets to target'),
  1394. ('port', 'ports to target [21]'),
  1395. ('user', 'usernames to test'),
  1396. ('password', 'passwords to test'),
  1397. )
  1398. available_options += TCP_Cache.available_options
  1399.  
  1400. Response = Response_Base
  1401.  
  1402. def new_tcp(self, host, port):
  1403. fp = FTP()
  1404. resp = fp.connect(host, int(port or 21))
  1405. return fp, resp
  1406.  
  1407. def execute(self, host, port=None, user=None, password=None, persistent='1'):
  1408. try:
  1409. fp, resp = self.get_tcp(persistent, host=host, port=port)
  1410.  
  1411. if user is not None:
  1412. resp = fp.sendcmd('USER ' + user)
  1413. if password is not None:
  1414. resp = fp.sendcmd('PASS ' + password)
  1415.  
  1416. logger.debug('No error: %s' % resp)
  1417.  
  1418. except FTP_Error as (resp,):
  1419. logger.debug('FTP_Error: %s' % resp)
  1420.  
  1421. except EOFError:
  1422. logger.debug('EOFError')
  1423. resp = '500 Connection reset by peer'
  1424.  
  1425. except socket.error:
  1426. logger.debug('socket.error')
  1427. resp = '500 Connection reset by peer'
  1428.  
  1429. code, mesg = resp.split(' ', 1)
  1430. return self.Response(code, mesg)
  1431.  
  1432. # }}}
  1433.  
  1434. # SSH {{{
  1435. try:
  1436. import paramiko
  1437. l = logging.getLogger('paramiko.transport')
  1438. l.setLevel(logging.CRITICAL)
  1439. l.addHandler(handler)
  1440. except ImportError:
  1441. warnings.append('paramiko')
  1442.  
  1443. class SSH_login(TCP_Cache):
  1444. '''Brute-force SSH authentication'''
  1445.  
  1446. usage_hints = (
  1447. """%prog host=10.0.0.1 user=root password=FILE0 0=passwords.txt"""
  1448. """ -x ignore:mesg='Authentication failed.' -x ignore,reset,retry:mesg='No existing session' -x reset:code=0""",
  1449. )
  1450.  
  1451. available_options = (
  1452. ('host', 'hostnames or subnets to target'),
  1453. ('port', 'ports to target [22]'),
  1454. ('user', 'usernames to test'),
  1455. ('password', 'passwords to test'),
  1456. ('auth_type', 'auth type to use [password|keyboard-interactive]'),
  1457. )
  1458. available_options += TCP_Cache.available_options
  1459.  
  1460. Response = Response_Base
  1461.  
  1462. cache_keys = ('host', 'port', 'user')
  1463. def new_tcp(self, host, port, user):
  1464. fp = paramiko.Transport('%s:%s' % (host, int(port or 22)))
  1465. fp.start_client()
  1466. return fp, fp.remote_version
  1467.  
  1468. def execute(self, host, port=None, user=None, password=None, persistent='1', auth_type='password'):
  1469. try:
  1470. fp, resp = self.get_tcp(persistent, host=host, port=port, user=user)
  1471.  
  1472. if user is not None and password is not None:
  1473. if auth_type == 'password':
  1474. fp.auth_password(user, password, fallback=False)
  1475.  
  1476. elif auth_type == 'keyboard-interactive':
  1477. fp.auth_interactive(user, lambda a,b,c: [password] if len(c) == 1 else [])
  1478.  
  1479. else:
  1480. raise NotImplementedError("Incorrect auth_type '%s'" % auth_type)
  1481.  
  1482. logger.debug('No error')
  1483. code, mesg = '0', resp
  1484.  
  1485. except paramiko.AuthenticationException as e:
  1486. logger.debug('AuthenticationException: %s' % e)
  1487. code, mesg = '1', str(e)
  1488.  
  1489. except paramiko.SSHException as e:
  1490. logger.debug('SSHException: %s' % e)
  1491. code, mesg = '1', str(e)
  1492.  
  1493. return self.Response(code, mesg)
  1494.  
  1495. # }}}
  1496.  
  1497. # Telnet {{{
  1498. from telnetlib import Telnet
  1499. class Telnet_login(TCP_Cache):
  1500. '''Brute-force Telnet authentication'''
  1501.  
  1502. usage_hints = (
  1503. """%prog host=10.0.0.1 inputs='FILE0\\nFILE1' 0=logins.txt 1=passwords.txt persistent=0"""
  1504. """ prompt_re='Username:|Password:' -x ignore:egrep='Login incorrect.+Username:'""",
  1505. )
  1506.  
  1507. available_options = (
  1508. ('host', 'hostnames or subnets to target'),
  1509. ('port', 'ports to target [23]'),
  1510. ('inputs', 'list of values to input'),
  1511. ('prompt_re', 'regular expression to match prompts [\w+]'),
  1512. ('timeout', 'seconds to wait for prompt_re to match received data [20]'),
  1513. )
  1514. available_options += TCP_Cache.available_options
  1515.  
  1516. Response = Response_Base
  1517.  
  1518. def new_tcp(self, host, port):
  1519. fp = Telnet(host, int(port or 23))
  1520. self.prompt_count = 0
  1521. return fp, None
  1522.  
  1523. def execute(self, host, port=None, inputs=None, prompt_re='\w+:', timeout='20', persistent='1'):
  1524. fp, _ = self.get_tcp(persistent, host=host, port=port)
  1525. trace = ''
  1526. timeout = int(timeout)
  1527.  
  1528. if self.prompt_count == 0:
  1529. _, _, raw = fp.expect([prompt_re], timeout=timeout)
  1530. logger.debug('raw banner: %s' % repr(raw))
  1531. trace += raw
  1532. self.prompt_count += 1
  1533.  
  1534. try:
  1535. for val in inputs.split(r'\n'):
  1536. logger.debug('input: %s' % val)
  1537. cmd = val + '\n' #'\r\x00'
  1538. fp.write(cmd)
  1539. trace += cmd
  1540.  
  1541. _, _, raw = fp.expect([prompt_re], timeout=timeout)
  1542. logger.debug('raw %d: %s' % (self.prompt_count, repr(raw)))
  1543. trace += raw
  1544. self.prompt_count += 1
  1545.  
  1546. mesg = repr(raw)[1:-1] # strip enclosing single quotes
  1547.  
  1548. except EOFError as e:
  1549. mesg = 'EOFError: %s' % e
  1550. logger.debug(mesg)
  1551.  
  1552. return self.Response('0', mesg, trace)
  1553.  
  1554. # }}}
  1555.  
  1556. # SMTP {{{
  1557. from smtplib import SMTP, SMTPAuthenticationError, SMTPHeloError, SMTPException
  1558. class SMTP_Base(TCP_Cache):
  1559.  
  1560. available_options = TCP_Cache.available_options
  1561. available_options += (
  1562. ('host', 'hostnames or subnets to target'),
  1563. ('port', 'ports to target [25]'),
  1564. ('helo', 'first command to send after connect [None]'),
  1565. ('user', 'usernames to test'),
  1566. )
  1567.  
  1568. Response = Response_Base
  1569.  
  1570. cache_keys = ('host', 'port', 'helo')
  1571.  
  1572. def new_tcp(self, host, port, helo):
  1573. fp = SMTP()
  1574. resp = fp.connect(host, int(port or 25))
  1575.  
  1576. if helo:
  1577. cmd, name = helo.split(' ', 1)
  1578.  
  1579. if cmd.lower() == 'ehlo':
  1580. resp = fp.ehlo(name)
  1581. else:
  1582. resp = fp.helo(name)
  1583.  
  1584. return fp, resp
  1585.  
  1586.  
  1587. class SMTP_vrfy(SMTP_Base):
  1588. '''Enumerate valid users using SMTP VRFY'''
  1589.  
  1590. usage_hints = (
  1591. '''%prog host=10.0.0.1 user=FILE0 0=logins.txt [helo='ehlo its.me.com']'''
  1592. ''' -x ignore:fgrep='User unknown' -x ignore,reset,retry:code=421''',
  1593. )
  1594.  
  1595. def execute(self, host, port=None, helo=None, user=None, persistent='1'):
  1596. fp, resp = self.get_tcp(persistent, host=host, port=port, helo=helo)
  1597.  
  1598. if user is not None:
  1599. resp = fp.verify(user)
  1600.  
  1601. code, mesg = resp
  1602. return self.Response(code, mesg)
  1603.  
  1604.  
  1605. class SMTP_rcpt(SMTP_Base):
  1606. '''Enumerate valid users using SMTP RCPT TO'''
  1607.  
  1608. usage_hints = (
  1609. '''%prog host=10.0.0.1 user=FILE0@localhost 0=logins.txt [helo='ehlo its.me.com']'''
  1610. ''' [[email protected]] -x ignore:fgrep='User unknown' -x ignore,reset,retry:code=421''',
  1611. )
  1612.  
  1613. available_options = SMTP_Base.available_options
  1614. available_options += (
  1615. ('mail_from', 'sender email [[email protected]]'),
  1616. )
  1617.  
  1618. def execute(self, host, port=None, helo=None, mail_from='[email protected]', user=None, persistent='1'):
  1619. fp, resp = self.get_tcp(persistent, host=host, port=port, helo=helo)
  1620.  
  1621. if mail_from:
  1622. resp = fp.mail(mail_from)
  1623.  
  1624. if user:
  1625. resp = fp.rcpt(user)
  1626.  
  1627. fp.rset()
  1628.  
  1629. code, mesg = resp
  1630. return self.Response(code, mesg)
  1631.  
  1632.  
  1633. class SMTP_login(SMTP_Base):
  1634. '''Brute-force SMTP authentication'''
  1635.  
  1636. usage_hints = (
  1637. '''%prog host=10.0.0.1 [email protected] password=FILE0 0=passwords.txt [helo='ehlo its.me.com']''',
  1638. ''' -x ignore:fgrep='Authentication failed' -x ignore,reset,retry:code=421''',
  1639. )
  1640.  
  1641. available_options = SMTP_Base.available_options
  1642. available_options += (
  1643. ('password', 'passwords to test'),
  1644. )
  1645.  
  1646. def execute(self, host, port=None, helo=None, user='', password='', persistent='1'):
  1647. fp, resp = self.get_tcp(persistent, host=host, port=port, helo=helo)
  1648.  
  1649. try:
  1650. resp = fp.login(user, password)
  1651.  
  1652. except (SMTPHeloError,SMTPAuthenticationError,SMTPException) as resp:
  1653. logger.debug('SMTPError: %s' % resp)
  1654.  
  1655. code, mesg = resp
  1656. return self.Response(code, mesg)
  1657.  
  1658. # }}}
  1659.  
  1660. # LDAP {{{
  1661. if not which('ldapsearch'):
  1662. warnings.append('openldap')
  1663.  
  1664. class Response_LDAP(Response_Base):
  1665. def __init__(self, resp):
  1666. self.code, self.out, self.err = resp
  1667. self.size = len(self.out + self.err)
  1668. self.mesg = ', '.join(p.strip() for p in self.out.splitlines() + self.err.splitlines())
  1669.  
  1670. def dump(self):
  1671. return '\n'.join(['out:', self.out, 'err:', self.err])
  1672.  
  1673. # Because python-ldap-2.4.4 did not allow using a PasswordPolicyControl
  1674. # during bind authentication (cf. http://article.gmane.org/gmane.comp.python.ldap/1003),
  1675. # I chose to wrap around ldapsearch with "-e ppolicy".
  1676.  
  1677. class LDAP_login:
  1678. '''Brute-force LDAP authentication'''
  1679.  
  1680. usage_hints = (
  1681. """%prog host=10.0.0.1 bindn='cn=Directory Manager' bindpw=FILE0 0=passwords.txt"""
  1682. """ -x ignore:mesg='ldap_bind: Invalid credentials (49)'""",
  1683. )
  1684.  
  1685. available_options = (
  1686. ('host', 'hostnames or subnets to target'),
  1687. ('port', 'ports to target [389]'),
  1688. ('binddn', 'usernames to test'),
  1689. ('bindpw', 'passwords to test'),
  1690. ('basedn', 'base DN for search'),
  1691. ('ssl', 'use SSL/TLS [0|1]'),
  1692. )
  1693. available_actions = ()
  1694.  
  1695. Response = Response_LDAP
  1696.  
  1697. def execute(self, host, port='389', binddn='', bindpw='', basedn='', ssl='0'):
  1698. uri = 'ldap%s://%s:%s' % ('s' if ssl != '0' else '', host, port)
  1699. cmd = ['ldapsearch', '-H', uri, '-e', 'ppolicy', '-D', binddn, '-w', bindpw, '-b', basedn]
  1700. p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={'LDAPTLS_REQCERT': 'never'})
  1701. out = p.stdout.read()
  1702. err = p.stderr.read()
  1703. code = p.wait()
  1704.  
  1705. return self.Response((code, out, err))
  1706.  
  1707. # }}}
  1708.  
  1709. # SMB {{{
  1710. try:
  1711. from impacket import smb as impacket_smb
  1712. except ImportError:
  1713. warnings.append('impacket')
  1714.  
  1715. class SMB_login(TCP_Cache):
  1716. '''Brute-force SMB authentication'''
  1717.  
  1718. usage_hints = (
  1719. """%prog host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt"""
  1720. """ -x ignore:fgrep=STATUS_LOGON_FAILURE -x reset:code=0""",
  1721. )
  1722.  
  1723. available_options = (
  1724. ('host', 'hostnames or subnets to target'),
  1725. ('port', 'ports to target [139]'),
  1726. ('user', 'usernames to test'),
  1727. ('password', 'passwords to test'),
  1728. ('password_hash', "LM/NT hashes to test, at least one hash must be provided ('lm:nt' or ':nt' or 'lm:')"),
  1729. ('domain', 'domains to test'),
  1730. )
  1731. available_options += TCP_Cache.available_options
  1732.  
  1733. Response = Response_Base
  1734.  
  1735. # ripped from medusa smbnt.c
  1736. error_map = {
  1737. 0xFF: 'UNKNOWN_ERROR_CODE',
  1738. 0x00: 'STATUS_SUCCESS',
  1739. 0x0D: 'STATUS_INVALID_PARAMETER',
  1740. 0x5E: 'STATUS_NO_LOGON_SERVERS',
  1741. 0x6D: 'STATUS_LOGON_FAILURE',
  1742. 0x6E: 'STATUS_ACCOUNT_RESTRICTION',
  1743. 0x6F: 'STATUS_INVALID_LOGON_HOURS',
  1744. 0x70: 'STATUS_INVALID_WORKSTATION',
  1745. 0x71: 'STATUS_PASSWORD_EXPIRED',
  1746. 0x72: 'STATUS_ACCOUNT_DISABLED',
  1747. 0x5B: 'STATUS_LOGON_TYPE_NOT_GRANTED',
  1748. 0x8D: 'STATUS_TRUSTED_RELATIONSHIP_FAILURE',
  1749. 0x93: 'STATUS_ACCOUNT_EXPIRED',
  1750. 0x24: 'STATUS_PASSWORD_MUST_CHANGE',
  1751. 0x34: 'STATUS_ACCOUNT_LOCKED_OUT',
  1752. 0x01: 'AS400_STATUS_LOGON_FAILURE',
  1753. }
  1754.  
  1755. def new_tcp(self, host, port):
  1756. fp = impacket_smb.SMB("*SMBSERVER", host, sess_port=int(port or 139))
  1757. return fp, fp.get_server_name()
  1758.  
  1759. def execute(self, host, port=None, user=None, password=None, password_hash=None, domain='', persistent='1'):
  1760. fp, mesg = self.get_tcp(persistent, host=host, port=port)
  1761.  
  1762. try:
  1763. if user is not None:
  1764. if password is not None:
  1765. fp.login(user, password, domain)
  1766.  
  1767. else:
  1768. lmhash, nthash = password_hash.split(':')
  1769. fp.login(user, '', domain, lmhash, nthash)
  1770.  
  1771. code = '0'
  1772.  
  1773. except impacket_smb.SessionError as e:
  1774. code = '%x-%x' % (e.error_class, e.error_code)
  1775. mesg = self.error_map.get(e.error_code, '')
  1776.  
  1777. error_class = e.error_classes.get(e.error_class, None) # (ERRNT, {})
  1778. if error_class:
  1779. class_str = error_class[0] # 'ERRNT'
  1780. error_tuple = error_class[1].get(e.error_code, None) # ('ERRnoaccess', 'Access denied.') or None
  1781.  
  1782. if error_tuple:
  1783. mesg += ' - %s %s' % error_tuple
  1784. else:
  1785. mesg += ' - %s' % class_str
  1786.  
  1787. return self.Response(code, mesg)
  1788.  
  1789. # }}}
  1790.  
  1791. # POP {{{
  1792. class Passd_Error(Exception): pass
  1793. class Passd:
  1794. def connect(self, host, port):
  1795. self.fp = socket.create_connection((host, port))
  1796. return self.getresp() # welcome banner
  1797.  
  1798. def close(self):
  1799. self.fp.close()
  1800.  
  1801. def sendcmd(self, cmd):
  1802. self.fp.sendall(cmd + '\r\n')
  1803. return self.getresp()
  1804.  
  1805. def getresp(self):
  1806. resp = self.fp.recv(1024)
  1807. while not resp.endswith('\r\n'):
  1808. resp += self.fp.recv(1024)
  1809.  
  1810. code, _ = self.unparse(resp)
  1811. if not code.startswith('2'):
  1812. raise Passd_Error, resp
  1813.  
  1814. return resp
  1815.  
  1816. def unparse(self, resp):
  1817. i = resp.rstrip().rfind('\n') + 1
  1818. code = resp[i:i+3]
  1819. mesg = resp[i+4:]
  1820.  
  1821. return code, mesg
  1822.  
  1823. class POP_passd:
  1824. '''Brute-force poppassd authentication (http://netwinsite.com/poppassd/ not POP3)'''
  1825.  
  1826. usage_hints = (
  1827. '''%prog host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt -x ignore:code=500''',
  1828. )
  1829.  
  1830. available_options = (
  1831. ('host', 'hostnames or subnets to target'),
  1832. ('port', 'ports to target [106]'),
  1833. ('user', 'usernames to test'),
  1834. ('password', 'passwords to test'),
  1835. )
  1836. available_actions = ()
  1837.  
  1838. Response = Response_Base
  1839.  
  1840. def execute(self, host, port=None, user=None, password=None):
  1841. try:
  1842. fp = Passd()
  1843. resp = fp.connect(host, int(port or 106))
  1844. trace = resp
  1845.  
  1846. if user is not None:
  1847. cmd = 'user %s' % user
  1848. resp = fp.sendcmd(cmd)
  1849. trace += '\r\n'.join((cmd, resp))
  1850.  
  1851. if password is not None:
  1852. cmd = 'pass %s' % password
  1853. resp = fp.sendcmd(cmd)
  1854. trace += '\r\n'.join((cmd, resp))
  1855.  
  1856. except Passd_Error as (resp,):
  1857. logger.debug('Passd_Error: %s' % resp)
  1858. trace += '\r\n'.join((cmd, resp))
  1859.  
  1860. finally:
  1861. fp.close()
  1862.  
  1863. code, mesg = fp.unparse(resp)
  1864. return self.Response(code, mesg, trace)
  1865.  
  1866. # }}}
  1867.  
  1868. # MySQL {{{
  1869. try:
  1870. import _mysql
  1871. except ImportError:
  1872. warnings.append('mysql-python')
  1873.  
  1874. class MySQL_login:
  1875. '''Brute-force MySQL authentication'''
  1876.  
  1877. usage_hints = (
  1878. """%prog host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt -x ignore:fgrep='Access denied for user'""",
  1879. )
  1880.  
  1881. available_options = (
  1882. ('host', 'hostnames or subnets to target'),
  1883. ('port', 'ports to target [3306]'),
  1884. ('user', 'usernames to test'),
  1885. ('password', 'passwords to test'),
  1886. )
  1887. available_actions = ()
  1888.  
  1889. Response = Response_Base
  1890.  
  1891. def execute(self, host, port=None, user='anony', password=''):
  1892.  
  1893. try:
  1894. fp = _mysql.connect(host=host, port=int(port or 3306), user=user, passwd=password)
  1895. resp = '0', fp.get_server_info()
  1896.  
  1897. except _mysql.Error, resp: pass
  1898.  
  1899. code, mesg = resp
  1900. return self.Response(code, mesg)
  1901.  
  1902. # }}}
  1903.  
  1904. # MSSQL {{{
  1905. # I did not use pymssql because neither version 1.x nor 2.0.0b1_dev were multithreads safe (they all segfault)
  1906. class MSSQL:
  1907. # ripped from medusa mssql.c
  1908. hdr = '\x02\x00\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1909. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
  1910.  
  1911. pt2 = '\x30\x30\x30\x30\x30\x30\x61\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1912. '\x00\x00\x00\x20\x18\x81\xb8\x2c\x08\x03\x01\x06\x0a\x09\x01\x01\x00\x00\x00\x00\x00\x00' \
  1913. '\x00\x00\x00\x73\x71\x75\x65\x6c\x64\x61\x20\x31\x2e\x30\x00\x00\x00\x00\x00\x00\x00\x00' \
  1914. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1915. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
  1916.  
  1917. pt3 = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1918. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1919. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1920. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1921. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1922. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1923. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1924. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1925. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1926. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1927. '\x00\x00\x00\x00\x04\x02\x00\x00\x4d\x53\x44\x42\x4c\x49\x42\x00\x00\x00\x07\x06\x00\x00' \
  1928. '\x00\x00\x0d\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1929. '\x00\x00\x00\x00\x00\x00'
  1930.  
  1931. langp = '\x02\x01\x00\x47\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00' \
  1932. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
  1933. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x30\x30\x00\x00' \
  1934. '\x00\x03\x00\x00\x00'
  1935.  
  1936. def connect(self, host, port):
  1937. self.fp = socket.create_connection((host, port))
  1938.  
  1939. def login(self, user, password):
  1940. MAX_LEN = 30
  1941. user_len = len(user)
  1942. password_len = len(password)
  1943. data = self.hdr + user[:MAX_LEN] + '\x00' * (MAX_LEN - user_len) + chr(user_len) + \
  1944. password[:MAX_LEN] + '\x00' * (MAX_LEN - password_len) + chr(password_len) + self.pt2 + chr(password_len) + \
  1945. password[:MAX_LEN] + '\x00' * (MAX_LEN - password_len) + self.pt3
  1946.  
  1947. self.fp.sendall(data)
  1948. self.fp.sendall(self.langp)
  1949.  
  1950. resp = self.fp.recv(1024)
  1951. code, size = self.parse(resp)
  1952.  
  1953. return code, size
  1954.  
  1955. def parse(self, resp):
  1956. i = 8
  1957. while True:
  1958. resp = resp[i:]
  1959. code, size = unpack('<cH', resp[:3])
  1960. #logger.debug('code: %s / size: %d' % (code.encode('hex'), size))
  1961.  
  1962. if code == '\xfd': # Done
  1963. break
  1964.  
  1965. if code in ('\xaa', '\xab') : # Error or Info message
  1966. num, state, severity, msg_len = unpack('IBBB', resp[3:10])
  1967. msg = resp[11:11+msg_len]
  1968. return num, msg
  1969.  
  1970. i = size + 3
  1971.  
  1972. raise Exception, 'Failed to parse response'
  1973.  
  1974. class MSSQL_login:
  1975. '''Brute-force MSSQL authentication'''
  1976.  
  1977. usage_hints = (
  1978. """%prog host=10.0.0.1 user=sa password=FILE0 0=passwords.txt -x ignore:fgrep='Login failed for user'""",
  1979. )
  1980.  
  1981. available_options = (
  1982. ('host', 'hostnames or subnets to target'),
  1983. ('port', 'ports to target [1433]'),
  1984. ('user', 'usernames to test'),
  1985. ('password', 'passwords to test'),
  1986. )
  1987. available_actions = ()
  1988.  
  1989. Response = Response_Base
  1990.  
  1991. def __init__(self):
  1992. self.m = MSSQL()
  1993.  
  1994. def execute(self, host, port=None, user='', password=''):
  1995. self.m.connect(host, int(port or 1433))
  1996. code, mesg = self.m.login(user, password)
  1997. return self.Response(code, mesg)
  1998.  
  1999. # }}}
  2000.  
  2001. # Oracle {{{
  2002. try:
  2003. import cx_Oracle
  2004. except ImportError:
  2005. warnings.append('cx_Oracle')
  2006.  
  2007. class Oracle_login:
  2008. '''Brute-force Oracle authentication'''
  2009.  
  2010. usage_hints = (
  2011. """%prog host=10.0.0.1 sid=FILE0 0=sids.txt -x ignore:code=ORA-12505""",
  2012. """%prog host=10.0.0.1 user=SYS password=FILE0 0=passwords.txt -x ignore:code=ORA-01017""",
  2013. )
  2014.  
  2015. available_options = (
  2016. ('host', 'hostnames or subnets to target'),
  2017. ('port', 'ports to target [1521]'),
  2018. ('user', 'usernames to test'),
  2019. ('password', 'passwords to test'),
  2020. ('sid', 'sid or service names to test'),
  2021. )
  2022. available_actions = ()
  2023.  
  2024. Response = Response_Base
  2025.  
  2026. def execute(self, host, port=None, user='', password='', sid=''):
  2027. dsn = cx_Oracle.makedsn(host, port or '1521', sid)
  2028. try:
  2029. fp = cx_Oracle.connect(user, password, dsn)
  2030. code, mesg = '0', fp.version
  2031.  
  2032. except cx_Oracle.DatabaseError as (e,):
  2033. code, mesg = e.message[:-1].split(': ', 1)
  2034.  
  2035. return self.Response(code, mesg)
  2036.  
  2037. # }}}
  2038.  
  2039. # PostgreSQL {{{
  2040. try:
  2041. import psycopg2
  2042. except ImportError:
  2043. warnings.append('psycopg')
  2044.  
  2045. class Pgsql_login:
  2046. '''Brute-force PostgreSQL authentication'''
  2047.  
  2048. usage_hints = (
  2049. """%prog host=10.0.0.1 user=postgres password=FILE0 0=passwords.txt -x ignore:fgrep='password authentication failed for user'""",
  2050. )
  2051.  
  2052. available_options = (
  2053. ('host', 'hostnames or subnets to target'),
  2054. ('port', 'ports to target [5432]'),
  2055. ('user', 'usernames to test'),
  2056. ('password', 'passwords to test'),
  2057. ('database', 'databases to test [postgres]'),
  2058. )
  2059. available_actions = ()
  2060.  
  2061. Response = Response_Base
  2062.  
  2063. def execute(self, host, port=None, user=None, password=None, database='postgres', ssl='disable'):
  2064. try:
  2065. psycopg2.connect(host=host, port=int(port or 5432), user=user, password=password, database=database, sslmode=ssl)
  2066. code, mesg = '0', 'OK'
  2067. except psycopg2.OperationalError as e:
  2068. code, mesg = '1', str(e)[:-1]
  2069.  
  2070. return self.Response(code, mesg)
  2071.  
  2072. # }}}
  2073.  
  2074. # HTTP {{{
  2075. from urllib import quote, urlencode
  2076. from urlparse import urlparse, urlunparse, parse_qsl
  2077. try:
  2078. import pycurl
  2079. except ImportError:
  2080. warnings.append('pycurl')
  2081.  
  2082. try:
  2083. from cStringIO import StringIO
  2084. except ImportError:
  2085. from StringIO import StringIO
  2086.  
  2087. class Controller_HTTP(Controller):
  2088. def expand_key(self, arg):
  2089. key, val = arg.split('=', 1)
  2090. if key == 'url':
  2091. m = re.match(r'(?:(?P<scheme>.+)://)?(?P<host>.+?)(?::(?P<port>[^/]+))?/'\
  2092. + '(?P<path>[^;?#]*)'\
  2093. + '(?:\;(?P<params>[^?#]*))?'\
  2094. + '(?:\?(?P<query>[^#]*))?'\
  2095. + '(?:\#(?P<fragment>.*))?' , val)
  2096.  
  2097. if not m:
  2098. yield (key, val)
  2099.  
  2100. else:
  2101. for k, v in m.groupdict().iteritems():
  2102. if v is not None:
  2103. yield (k, v)
  2104. else:
  2105. yield (key, val)
  2106.  
  2107. class Response_HTTP(Response_Base):
  2108.  
  2109. def __init__(self, code, content_length, response, trace):
  2110. self.code, self.content_length = code, content_length
  2111. self.response, self.trace = response, trace
  2112. self.size = len(self.response)
  2113.  
  2114. def compact(self):
  2115. return '%s %s' % (self.code, '%d:%d' % (self.size, self.content_length))
  2116.  
  2117. def __str__(self):
  2118. i = self.response.rfind('HTTP/', 0, 5000)
  2119. if i == -1:
  2120. return ''
  2121. else:
  2122. j = self.response.find('\n', i)
  2123. line = self.response[i:j]
  2124. return line.strip()
  2125.  
  2126. def match_clen(self, val):
  2127. return match_size(self.content_length, val)
  2128.  
  2129. def match_fgrep(self, val):
  2130. return val in self.response
  2131.  
  2132. def match_egrep(self, val):
  2133. return re.search(val, self.response, re.M)
  2134.  
  2135. available_conditions = Response_Base.available_conditions
  2136. available_conditions += (
  2137. ('clen', 'match Content-Length header (N or N-M or N- or -N)'),
  2138. )
  2139.  
  2140. class HTTP_fuzz(TCP_Cache):
  2141. '''Fuzz HTTP/HTTPS'''
  2142.  
  2143. usage_hints = [
  2144. """%prog url=http://10.0.0.1/FILE0 0=paths.txt -x ignore:code=404 -x ignore,retry:code=500""",
  2145.  
  2146. """%prog url=http://10.0.0.1/manager/html user_pass=COMBO00:COMBO01 0=combos.txt"""
  2147. """ -x ignore:code=401""",
  2148.  
  2149. """%prog url=http://10.0.0.1/phpmyadmin/index.php method=POST"""
  2150. """ body='pma_username=root&pma_password=FILE0&server=1&lang=en' 0=passwords.txt follow=1"""
  2151. """ accept_cookie=1 -x ignore:fgrep='Cannot log in to the MySQL server'""",
  2152. ]
  2153.  
  2154. available_options = (
  2155. ('url', 'main url to target (scheme://host[:port]/path?query)'),
  2156. #('host', 'hostnames or subnets to target'),
  2157. #('port', 'ports to target'),
  2158. #('scheme', 'scheme [http | https]'),
  2159. #('path', 'web path [/]'),
  2160. #('query', 'query string'),
  2161. ('body', 'body data'),
  2162. ('header', 'use custom headers, delimited with "\\r\\n"'),
  2163. ('method', 'method to use [GET | POST | HEAD | ...]'),
  2164. ('user_pass', 'username and password for HTTP authentication (user:pass)'),
  2165. ('auth_type', 'type of HTTP authentication [basic | digest | ntlm]'),
  2166. ('follow', 'follow any Location redirect [0|1]'),
  2167. ('max_follow', 'redirection limit [5]'),
  2168. ('accept_cookie', 'save received cookies to issue them in future requests [0|1]'),
  2169. ('http_proxy', 'HTTP proxy to use (host:port)'),
  2170. ('ssl_cert', 'client SSL certificate file (cert+key in PEM format)'),
  2171. ('timeout_tcp', 'seconds to wait for a TCP handshake [10]'),
  2172. ('timeout', 'seconds to wait for a HTTP response [20]'),
  2173. ('before_urls', 'comma-separated URLs to query before main url'),
  2174. ('after_urls', 'comma-separated URLs to query after main url'),
  2175. ('max_mem', 'store no more than N bytes of request+response data in memory [-1 (unlimited)]'),
  2176. )
  2177. available_options += TCP_Cache.available_options
  2178.  
  2179. Response = Response_HTTP
  2180.  
  2181. cache_keys = ('host', 'port', 'scheme')
  2182. def new_tcp(self, host, port, scheme):
  2183. fp = pycurl.Curl()
  2184. fp.setopt(pycurl.SSL_VERIFYPEER, 0)
  2185. fp.setopt(pycurl.SSL_VERIFYHOST, 0)
  2186. fp.setopt(pycurl.HEADER, 1)
  2187. fp.setopt(pycurl.USERAGENT, 'Mozilla/5.0')
  2188. fp.setopt(pycurl.NOSIGNAL, 1)
  2189.  
  2190. return fp, None
  2191.  
  2192. def execute(self, url=None, host=None, port=None, scheme='http', path='/', params='', query='', fragment='', body='', header='', method='GET', user_pass='', auth_type='basic',
  2193. follow='0', max_follow='5', accept_cookie='0', http_proxy='', ssl_cert='', timeout_tcp='10', timeout='20', persistent='1',
  2194. before_urls='', after_urls='', max_mem='-1'):
  2195.  
  2196. if url:
  2197. scheme, host, path, params, query, fragment = urlparse(url)
  2198. if ':' in host:
  2199. host, port = host.split(':')
  2200. del url
  2201.  
  2202. fp, _ = self.get_tcp(persistent, host=host, port=port, scheme=scheme)
  2203.  
  2204. fp.setopt(pycurl.FOLLOWLOCATION, int(follow))
  2205. fp.setopt(pycurl.MAXREDIRS, int(max_follow))
  2206. fp.setopt(pycurl.CONNECTTIMEOUT, int(timeout_tcp))
  2207. fp.setopt(pycurl.TIMEOUT, int(timeout))
  2208. fp.setopt(pycurl.PROXY, http_proxy)
  2209.  
  2210. def noop(buf): pass
  2211. fp.setopt(pycurl.WRITEFUNCTION, noop)
  2212.  
  2213. def debug_func(t, s):
  2214. if max_mem > 0 and trace.tell() > max_mem:
  2215. return 0
  2216.  
  2217. if t in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT):
  2218. trace.write(s)
  2219.  
  2220. elif t in (pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN):
  2221. trace.write(s)
  2222. response.write(s)
  2223.  
  2224. max_mem = int(max_mem)
  2225. response, trace = StringIO(), StringIO()
  2226.  
  2227. fp.setopt(pycurl.DEBUGFUNCTION, debug_func)
  2228. fp.setopt(pycurl.VERBOSE, 1)
  2229.  
  2230. if user_pass:
  2231. fp.setopt(pycurl.USERPWD, user_pass)
  2232. if auth_type == 'basic':
  2233. fp.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
  2234. elif auth_type == 'digest':
  2235. fp.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)
  2236. elif auth_type == 'ntlm':
  2237. fp.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_NTLM)
  2238. else:
  2239. raise NotImplementedError("Incorrect auth_type '%s'" % auth_type)
  2240.  
  2241. if ssl_cert:
  2242. fp.setopt(pycurl.SSLCERT, ssl_cert)
  2243.  
  2244. headers = [h.strip('\r') for h in header.split('\n') if h]
  2245. fp.setopt(pycurl.HTTPHEADER, headers) # warning: this disables the use of "Expect: 100-continue" header
  2246.  
  2247. if accept_cookie == '1':
  2248. fp.setopt(pycurl.COOKIEFILE, '')
  2249. # warning: do not pass a Cookie: header into HTTPHEADER if using COOKIEFILE as it will
  2250. # produce requests with more than one Cookie: header
  2251. # and the server will process only one of them (eg. Apache only reads the last one)
  2252.  
  2253. #if rrange: # commented out because the user may instead pass header='Range: -1024'
  2254. # fp.setopt(pycurl.RANGE, rrange)
  2255.  
  2256. def setup_fp(fp, method, url):
  2257. if method == 'GET':
  2258. fp.setopt(pycurl.HTTPGET, 1)
  2259.  
  2260. elif method == 'POST':
  2261. fp.setopt(pycurl.POST, 1)
  2262. fp.setopt(pycurl.POSTFIELDS, body)
  2263.  
  2264. elif method == 'HEAD':
  2265. fp.setopt(pycurl.NOBODY, 1)
  2266.  
  2267. else:
  2268. fp.setopt(pycurl.CUSTOMREQUEST, method)
  2269.  
  2270. #logger.debug('url: %s' % url)
  2271. fp.setopt(pycurl.URL, url)
  2272.  
  2273. if before_urls:
  2274. for before_url in before_urls.split(','):
  2275. setup_fp(fp, 'GET', before_url)
  2276. fp.perform()
  2277.  
  2278. path = quote(path)
  2279. query = urlencode(parse_qsl(query, True))
  2280. body = urlencode(parse_qsl(body, True))
  2281.  
  2282. if port:
  2283. host = '%s:%s' % (host, port)
  2284.  
  2285. url = urlunparse((scheme, host, path, params, query, fragment))
  2286. setup_fp(fp, method, url)
  2287. fp.perform()
  2288.  
  2289. if after_urls:
  2290. for after_url in after_urls.split(','):
  2291. setup_fp(fp, 'GET', after_url)
  2292. fp.perform()
  2293.  
  2294. http_code = fp.getinfo(pycurl.HTTP_CODE)
  2295. content_length = fp.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD)
  2296.  
  2297. return self.Response(http_code, content_length, response.getvalue(), trace.getvalue())
  2298.  
  2299. # }}}
  2300.  
  2301. # VNC {{{
  2302. try:
  2303. from Crypto.Cipher import DES
  2304. except ImportError:
  2305. warnings.append('pycrypto')
  2306.  
  2307. class VNC_Error(Exception): pass
  2308. class VNC:
  2309. def connect(self, host, port):
  2310. self.fp = socket.create_connection((host, port))
  2311. resp = self.fp.recv(1024) # banner
  2312. self.version = resp[:11]
  2313.  
  2314. if len(resp) > 12:
  2315. raise VNC_Error, self.version + ' ' + resp[20:]
  2316.  
  2317. return self.version
  2318.  
  2319. def login(self, password):
  2320. logger.debug("Remote version: %s" % self.version)
  2321. major, minor = self.version[6], self.version[10]
  2322.  
  2323. if major == '3' and minor == '8':
  2324. proto = 'RFB 003.008\n'
  2325.  
  2326. elif major == '3' and minor == '7':
  2327. proto = 'RFB 003.007\n'
  2328.  
  2329. else:
  2330. proto = 'RFB 003.003\n'
  2331.  
  2332. logger.debug('Client version: %s' % proto[:-1])
  2333. self.fp.sendall(proto)
  2334.  
  2335. if minor in ('7', '8'):
  2336. # send security type
  2337. resp = self.fp.recv(1024)
  2338. logger.debug("Security types supported: %s" % repr(resp))
  2339. self.fp.sendall('\x02') # always use classic VNC authentication
  2340.  
  2341. # read server challenge
  2342. resp = self.fp.recv(1024)
  2343. logger.debug('Remote challenge: %s' % repr(resp))
  2344.  
  2345. if minor == '3':
  2346. if len(resp) < 4:
  2347. raise VNC_Error, 'Unexpected response size (%d > 4): %s' % (len(resp), repr(resp))
  2348.  
  2349. code = ord(resp[3])
  2350. if code == 0:
  2351. raise VNC_Error, 'Session setup failed: %s' % repr(resp)
  2352.  
  2353. elif code == 1:
  2354. raise VNC_Error, 'No authentication required: %s' % repr(resp)
  2355.  
  2356. elif code == 2:
  2357. if len(resp) != 20:
  2358. raise VNC_Error, 'Unexpected challenge size (unsupported authentication type ?): %s' % repr(resp)
  2359.  
  2360. resp = resp[4:20]
  2361.  
  2362. else:
  2363. raise VNC_Error, 'Session setup unknown response'
  2364.  
  2365. pw = (password + '\0' * 8)[:8] # make sure it is 8 chars long, zero padded
  2366. key = self.gen_key(pw)
  2367. logger.debug('key: %s' % repr(key))
  2368.  
  2369. des = DES.new(key, DES.MODE_ECB)
  2370. enc = des.encrypt(resp)
  2371. logger.debug('enc: %s' % repr(enc))
  2372.  
  2373. self.fp.sendall(enc)
  2374. resp = self.fp.recv(1024)
  2375. logger.debug('resp: %s' % repr(resp))
  2376.  
  2377. code = ord(resp[3])
  2378. mesg = resp[8:]
  2379.  
  2380. if code == 1:
  2381. return code, mesg or 'Authentication failure'
  2382.  
  2383. elif code == 0:
  2384. return mesg or 'OK'
  2385.  
  2386. else:
  2387. raise VNC_Error, 'Unknown response: %s (code: %s)' % (repr(resp), code)
  2388.  
  2389.  
  2390. def gen_key(self, key):
  2391. newkey = []
  2392. for ki in range(len(key)):
  2393. bsrc = ord(key[ki])
  2394. btgt = 0
  2395. for i in range(8):
  2396. if bsrc & (1 << i):
  2397. btgt = btgt | (1 << 7-i)
  2398. newkey.append(chr(btgt))
  2399. return ''.join(newkey)
  2400.  
  2401.  
  2402. class VNC_login:
  2403. '''Brute-force VNC authentication'''
  2404.  
  2405. usage_hints = (
  2406. """%prog host=10.0.0.1 password=FILE0 0=passwords.txt -x retry:fgrep!='Authentication failure' --max-retries -1 -x quit:code=0""",
  2407. )
  2408.  
  2409. available_options = (
  2410. ('host', 'hostnames or subnets to target'),
  2411. ('port', 'ports to target [5900]'),
  2412. ('password', 'passwords to test'),
  2413. )
  2414. available_actions = ()
  2415.  
  2416. Response = Response_Base
  2417.  
  2418. def __init__(self):
  2419. self.m = VNC()
  2420. def execute(self, host, port=None, password=None):
  2421. try:
  2422. code, mesg = '0', self.m.connect(host, int(port or 5900))
  2423.  
  2424. if password is not None:
  2425. code, mesg = self.m.login(password)
  2426.  
  2427. except VNC_Error as (e,):
  2428. logger.debug('VNC_Error: %s' % e)
  2429. code, mesg = '2', e
  2430.  
  2431. return self.Response(code, mesg)
  2432.  
  2433. # }}}
  2434.  
  2435. # DNS {{{
  2436. class HostInfo:
  2437. def __init__(self):
  2438. self.name = set()
  2439. self.ip = set()
  2440. self.alias = set()
  2441.  
  2442. def __str__(self):
  2443. line = ''
  2444. if self.name:
  2445. line = ' '.join(self.name)
  2446. if self.ip:
  2447. if line: line += ' / '
  2448. line += ' '.join(map(str, self.ip))
  2449. if self.alias:
  2450. if line: line += ' / '
  2451. line += ' '.join(self.alias)
  2452.  
  2453. return line
  2454.  
  2455. class Controller_DNS(Controller):
  2456. hostmap = {}
  2457.  
  2458. # show_final {{{
  2459. def show_final(self):
  2460. '''
  2461. 1.2.3.4 ftp.example.com
  2462. . www.example.com
  2463. . www2.example.com
  2464. noip cms.example.com -> www.mistake.com
  2465. '''
  2466. ipmap = {}
  2467. noips = set()
  2468.  
  2469. '''
  2470. hostmap = {
  2471. 'ftp.example.com': {'ip': ['1.2.3.4'], 'alias': []},
  2472. 'www.example.com': {'ip': ['1.2.3.4'], 'alias': ['www2.example.com']},
  2473. 'www.mistake.com': {'ip': [], 'alias': ['cms.example.com']}, ...}
  2474. ipmap = {'1.2.3.4': {'name': ['www.example.com', 'ftp.example.com'], 'alias': ('www2.example.com')}}
  2475. noips = ['cms.example.com -> www.mistake.com', ...]
  2476. '''
  2477. for name, hinfo in self.hostmap.iteritems():
  2478. logger.debug('%s -> %s' % (name, hinfo))
  2479. if not hinfo.ip: # orphan CNAME hostnames (with no IP address) may be still valid virtual hosts
  2480. for alias in hinfo.alias:
  2481. noips.add('%s -> %s' % (alias, name))
  2482. else:
  2483. for ip in hinfo.ip:
  2484. if ip not in ipmap: ipmap[ip] = HostInfo()
  2485. ipmap[ip].name.add(name)
  2486. ipmap[ip].alias.update(hinfo.alias)
  2487.  
  2488. # pretty print
  2489. def pprint_info(key, infos):
  2490. first = True
  2491. for info in infos:
  2492. if first:
  2493. print('%34s %s' % (info, key))
  2494. first = False
  2495. else:
  2496. print('%34s %s' % (info, key))
  2497.  
  2498. print('Hostmap ' + '-'*42)
  2499. for ip, hinfo in sorted(ipmap.iteritems()):
  2500. pprint_info( ip, hinfo.name)
  2501. pprint_info('.', hinfo.alias)
  2502.  
  2503. pprint_info('noip', noips)
  2504.  
  2505. print('Domains ' + '-'*42)
  2506. domains = {}
  2507. networks = {}
  2508. for ip, hinfo in ipmap.iteritems():
  2509. for name in hinfo.name:
  2510. i = 1 if name.count('.') > 1 else 0
  2511. d = '.'.join(name.split('.')[i:])
  2512. if d not in domains: domains[d] = 0
  2513. domains[d] += 1
  2514.  
  2515. for domain, count in sorted(domains.iteritems(), key=lambda a:a[0].split('.')[-1::-1]):
  2516. print('%34s %d' % (domain, count))
  2517.  
  2518. print('Networks ' + '-'*41)
  2519. nets = {}
  2520. for ip in set(ipmap):
  2521. if not ip.version() == 4:
  2522. nets[ip] = [ip]
  2523. else:
  2524. n = ip.make_net('255.255.255.0')
  2525. if n not in nets: nets[n] = []
  2526. nets[n].append(ip)
  2527.  
  2528. for net, ips in sorted(nets.iteritems()):
  2529. if len(ips) == 1:
  2530. print(' '*10 + '%39s' % ips[0])
  2531. else:
  2532. print(' '*10 + '%37s.x' % '.'.join(str(net).split('.')[:-1]))
  2533.  
  2534. # }}}
  2535.  
  2536. def push_final(self, resp):
  2537. for name, hinfo in resp.hostmap.iteritems():
  2538. if name not in self.hostmap:
  2539. self.hostmap[name] = hinfo
  2540. else:
  2541. self.hostmap[name].ip.update(hinfo.ip)
  2542. self.hostmap[name].alias.update(hinfo.alias)
  2543.  
  2544. def generate_tld():
  2545. gtld = [
  2546. 'aero', 'arpa', 'asia', 'biz', 'cat', 'com', 'coop', 'edu',
  2547. 'gov', 'info', 'int', 'jobs', 'mil', 'mobi', 'museum', 'name',
  2548. 'net', 'org', 'pro', 'tel', 'travel']
  2549.  
  2550. cctld = [''.join(i) for i in product(*[ascii_lowercase]*2)]
  2551. tld = gtld + cctld
  2552. return tld, len(tld)
  2553.  
  2554. def generate_srv():
  2555. common = [
  2556. '_gc._tcp', '_kerberos._tcp', '_kerberos._udp', '_ldap._tcp',
  2557. '_test._tcp', '_sips._tcp', '_sip._udp', '_sip._tcp', '_aix._tcp', '_aix._udp',
  2558. '_finger._tcp', '_ftp._tcp', '_http._tcp', '_nntp._tcp', '_telnet._tcp',
  2559. '_whois._tcp', '_h323cs._tcp', '_h323cs._udp', '_h323be._tcp', '_h323be._udp',
  2560. '_h323ls._tcp', '_h323ls._udp', '_sipinternal._tcp', '_sipinternaltls._tcp',
  2561. '_sip._tls', '_sipfederationtls._tcp', '_jabber._tcp', '_xmpp-server._tcp', '_xmpp-client._tcp',
  2562. '_imap.tcp', '_certificates._tcp', '_crls._tcp', '_pgpkeys._tcp', '_pgprevokations._tcp',
  2563. '_cmp._tcp', '_svcp._tcp', '_crl._tcp', '_ocsp._tcp', '_PKIXREP._tcp',
  2564. '_smtp._tcp', '_hkp._tcp', '_hkps._tcp', '_jabber._udp', '_xmpp-server._udp',
  2565. '_xmpp-client._udp', '_jabber-client._tcp', '_jabber-client._udp',
  2566. '_adsp._domainkey', '_policy._domainkey', '_domainkey', '_ldap._tcp.dc._msdcs', '_ldap._udp.dc._msdcs']
  2567.  
  2568. def distro():
  2569. import os
  2570. import re
  2571. files = ['/usr/share/nmap/nmap-protocols', '/usr/share/nmap/nmap-services', '/etc/protocols', '/etc/services']
  2572. ret = []
  2573. for f in files:
  2574. if not os.path.isfile(f):
  2575. logger.warn("File '%s' is missing, there will be less records to test" % f)
  2576. continue
  2577. for line in open(f):
  2578. match = re.match(r'([a-zA-Z0-9]+)\s', line)
  2579. if not match: continue
  2580. for w in re.split(r'[^a-z0-9]', match.group(1).strip().lower()):
  2581. ret.extend(['_%s.%s' % (w, i) for i in ('_tcp', '_udp')])
  2582. return ret
  2583.  
  2584. srv = set(common + distro())
  2585. return srv, len(srv)
  2586.  
  2587. try:
  2588. from DNS import DnsRequest, DNSError
  2589. except ImportError:
  2590. warnings.append('pydns')
  2591.  
  2592. class DNS_reverse:
  2593. '''Reverse lookup subnets'''
  2594.  
  2595. usage_hints = [
  2596. """%prog host=NET0 0=192.168.0.0/24 -x ignore:code=3""",
  2597. """%prog host=NET0 0=216.239.32.0-216.239.47.255,8.8.8.0/24 -x ignore:code=3 -x ignore:fgrep!=google.com -x ignore:fgrep=216-239-""",
  2598. ]
  2599.  
  2600. available_options = (
  2601. ('host', 'IP addresses to reverse'),
  2602. ('server', 'name server to query (directly asking a zone authoritative NS may return more results) [8.8.8.8]'),
  2603. ('timeout', 'seconds to wait for a DNS response [10]'),
  2604. )
  2605. available_actions = ()
  2606.  
  2607. Response = Response_Base
  2608.  
  2609. def execute(self, host, server='8.8.8.8', timeout='10'):
  2610. resolver = DnsRequest(qtype='PTR', server=server, timeout=int(timeout))
  2611.  
  2612. ip = IP(host)
  2613. ptr = ip.reverseName()
  2614. result = resolver.req(ptr.rstrip('.'))
  2615. hostnames = [ans['data'] for ans in result.answers]
  2616.  
  2617. hostmap = {}
  2618. for n in hostnames:
  2619. if n not in hostmap: hostmap[n] = HostInfo()
  2620. hostmap[n].ip.add(ip)
  2621.  
  2622. code = result.header['rcode']
  2623. status = result.header['status']
  2624. mesg = '%s %s' % (status, ', '.join(hostnames))
  2625.  
  2626. resp = self.Response(code, mesg)
  2627. resp.hostmap = hostmap
  2628.  
  2629. return resp
  2630.  
  2631. class DNS_forward:
  2632. '''Forward lookup subdomains'''
  2633.  
  2634. usage_hints = [
  2635. """%prog domain=FILE0.google.com 0=names.txt -x ignore:code=3""",
  2636. """%prog domain=google.MOD0 0=TLD -x ignore:code=3""",
  2637. """%prog domain=MOD0.microsoft.com 0=SRV qtype=SRV -x ignore:code=3""",
  2638. ]
  2639.  
  2640. available_options = (
  2641. ('domain', 'domains to lookup'),
  2642. ('server', 'name server to query (directly asking the zone authoritative NS may return more results) [8.8.8.8]'),
  2643. ('timeout', 'seconds to wait for a DNS response [10]'),
  2644. ('qtype', 'comma-separated list of types to query [ANY,A,AAAA]'),
  2645. )
  2646. available_actions = ()
  2647.  
  2648. available_keys = {
  2649. 'TLD': generate_tld,
  2650. 'SRV': generate_srv,
  2651. }
  2652.  
  2653. Response = Response_Base
  2654.  
  2655. def execute(self, domain, server='8.8.8.8', timeout='10', qtype='ANY,A,AAAA'):
  2656. resolver = DnsRequest(server=server, timeout=int(timeout))
  2657.  
  2658. hostmap = {}
  2659. for qt in qtype.split(','):
  2660. result = resolver.req(domain, qtype=qt.strip())
  2661.  
  2662. for r in result.answers + result.additional + result.authority:
  2663. t = r['typename']
  2664. n = r['name']
  2665. d = r['data']
  2666.  
  2667. if t not in ('A', 'AAAA', 'CNAME', 'DNAME', 'SRV'):
  2668. continue
  2669.  
  2670. if t == 'SRV':
  2671. _, _, _, d = d
  2672.  
  2673. if t in ('CNAME', 'DNAME', 'SRV'):
  2674. n, d = d, n
  2675.  
  2676. if n not in hostmap:
  2677. hostmap[n] = HostInfo()
  2678.  
  2679. if t == 'A':
  2680. hostmap[n].ip.add(IP(d))
  2681.  
  2682. elif t == 'AAAA':
  2683. hostmap[n].ip.add(IP(hexlify(d)))
  2684.  
  2685. elif t in ('CNAME', 'DNAME'):
  2686. hostmap[n].alias.add(d)
  2687.  
  2688. elif t == 'SRV':
  2689. hostmap[n].alias.add(d)
  2690.  
  2691. code = result.header['rcode']
  2692. status = result.header['status']
  2693. mesg = '%s %s' % (status, ' | '.join('%s / %s' % (k, v) for k, v in hostmap.iteritems()))
  2694.  
  2695. resp = self.Response(code, mesg)
  2696. resp.hostmap = hostmap
  2697.  
  2698. return resp
  2699.  
  2700. # }}}
  2701.  
  2702. # SNMP {{{
  2703. try:
  2704. from pysnmp.entity.rfc3413.oneliner import cmdgen
  2705. except ImportError:
  2706. warnings.append('pysnmp')
  2707.  
  2708. class SNMP_login:
  2709. '''Brute-force SNMP v1/2/3 authentication'''
  2710.  
  2711. usage_hints = (
  2712. """%prog host=10.0.0.1 version=2 community=FILE0 1=names.txt -x ignore:mesg='No SNMP response received before timeout'""",
  2713. """%prog host=10.0.0.1 version=3 user=FILE0 0=logins.txt -x ignore:mesg=unknownUserName""",
  2714. """%prog host=10.0.0.1 version=3 user=myuser auth_key=FILE0 0=passwords.txt -x ignore:mesg=wrongDigest""",
  2715. )
  2716.  
  2717. available_options = (
  2718. ('host', 'hostnames or subnets to target'),
  2719. ('port', 'ports to target [161]'),
  2720. ('version', 'SNMP version to use [2|3|1]'),
  2721. #('security_name', 'SNMP v1/v2 username, for most purposes it can be any arbitrary string [test-agent]'),
  2722. ('community', 'SNMPv1/2c community names to test [public]'),
  2723. ('user', 'SNMPv3 usernames to test [myuser]'),
  2724. ('auth_key', 'SNMPv3 pass-phrases to test [my_password]'),
  2725. #('priv_key', 'SNMP v3 secret key for encryption'), # see http://pysnmp.sourceforge.net/docs/4.x/index.html#UsmUserData
  2726. #('auth_protocol', ''),
  2727. #('priv_protocol', ''),
  2728. ('timeout', 'seconds to wait for a response [1]'),
  2729. ('retries', 'number of successive request retries [2]'),
  2730. )
  2731. available_actions = ()
  2732.  
  2733. Response = Response_Base
  2734.  
  2735. def execute(self, host, port=None, version='2', community='public', user='myuser', auth_key='my_password', timeout='1', retries='2'):
  2736. if version in ('1', '2'):
  2737. security_model = cmdgen.CommunityData('test-agent', community, 0 if version == '1' else 1)
  2738.  
  2739. elif version == '3':
  2740. security_model = cmdgen.UsmUserData(user, auth_key) # , priv_key)
  2741. if len(auth_key) < 8:
  2742. return self.Response('1', 'SNMPv3 requires passphrases to be at least 8 characters long')
  2743.  
  2744. else:
  2745. raise NotImplementedError("Incorrect SNMP version '%s'" % version)
  2746.  
  2747. errorIndication, errorStatus, errorIndex, varBinds = cmdgen.CommandGenerator().getCmd(
  2748. security_model,
  2749. cmdgen.UdpTransportTarget((host, int(port or 161)), timeout=int(timeout), retries=int(retries)),
  2750. (1,3,6,1,2,1,1,1,0)
  2751. )
  2752.  
  2753. code = '%d-%d' % (errorStatus, errorIndex)
  2754. if not errorIndication:
  2755. mesg = '%s' % varBinds
  2756. else:
  2757. mesg = '%s' % errorIndication
  2758.  
  2759. return self.Response(code, mesg)
  2760.  
  2761. # }}}
  2762.  
  2763. # Unzip {{{
  2764. if not which('unzip'):
  2765. warnings.append('unzip')
  2766.  
  2767. class Response_Unzip(Response_Base):
  2768. def __init__(self, resp):
  2769. self.code, self.out, self.err = resp
  2770. self.size = len(self.out + self.err)
  2771. if '\n' in self.out:
  2772. self.mesg = self.out.splitlines()[-1]
  2773. else:
  2774. self.mesg = self.out
  2775.  
  2776. def __str__(self):
  2777. return '%s [%s] %s' % (self.code, self.size, self.mesg)
  2778.  
  2779. def dump(self):
  2780. return 'out: %s\n\nerr: %s' % (self.out, self.err)
  2781.  
  2782. class Unzip_pass:
  2783. '''Brute-force the password of encrypted ZIP files'''
  2784.  
  2785. usage_hints = [
  2786. """%prog zipfile=path/to/file.zip password=FILE0 0=passwords.txt -x ignore:code!=0""",
  2787. ]
  2788.  
  2789. available_options = (
  2790. ('zipfile', 'ZIP files to test'),
  2791. ('password', 'passwords to test'),
  2792. )
  2793.  
  2794. available_actions = ()
  2795.  
  2796. Response = Response_Unzip
  2797.  
  2798. def execute(self, zipfile, password):
  2799. zipfile = os.path.abspath(zipfile)
  2800. cmd = ['unzip', '-t', '-q', '-P', password, zipfile]
  2801. p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  2802. out = p.stdout.read()
  2803. err = p.stderr.read()
  2804. code = p.wait()
  2805.  
  2806. return self.Response((code, out, err))
  2807.  
  2808. # }}}
  2809.  
  2810. # Keystore {{{
  2811. if not which('keytool'):
  2812. warnings.append('java')
  2813.  
  2814. class Response_Keystore(Response_Base):
  2815. def __init__(self, resp):
  2816. self.code, self.out, self.err = resp
  2817. self.size = len(self.out + self.err)
  2818. self.mesg = self.out.replace('\n', ' ')
  2819.  
  2820. def __str__(self):
  2821. return '%s [%s] %s' % (self.code, self.size, self.mesg)
  2822.  
  2823. def dump(self):
  2824. return 'out: %s\nerr: %s' % (self.out, self.err)
  2825.  
  2826. class Keystore_pass:
  2827. '''Brute-force the password of Java keystore files'''
  2828.  
  2829. usage_hints = [
  2830. """%prog keystore=path/to/keystore.jks password=FILE0 0=passwords.txt -x ignore:fgrep='password was incorrect'""",
  2831. ]
  2832.  
  2833. available_options = (
  2834. ('keystore', 'keystore files to test'),
  2835. ('password', 'passwords to test'),
  2836. ('storetype', 'type of keystore to test'),
  2837. )
  2838.  
  2839. available_actions = ()
  2840.  
  2841. Response = Response_Keystore
  2842.  
  2843. def execute(self, keystore, password, storetype='jks'):
  2844. keystore = os.path.abspath(keystore)
  2845. cmd = ['keytool', '-list', '-keystore', keystore, '-storepass', password, '-storetype', storetype]
  2846. p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  2847. out = p.stdout.read()
  2848. err = p.stderr.read()
  2849. code = p.wait()
  2850.  
  2851. return self.Response((code, out, err))
  2852.  
  2853. # }}}
  2854.  
  2855. # modules {{{
  2856. modules = (
  2857. 'ftp_login', (Controller, FTP_login),
  2858. 'ssh_login', (Controller, SSH_login),
  2859. 'telnet_login', (Controller, Telnet_login),
  2860. 'smtp_login', (Controller, SMTP_login),
  2861. 'smtp_vrfy', (Controller, SMTP_vrfy),
  2862. 'smtp_rcpt', (Controller, SMTP_rcpt),
  2863. 'http_fuzz', (Controller_HTTP, HTTP_fuzz),
  2864. 'pop_passd', (Controller, POP_passd),
  2865. 'smb_login', (Controller, SMB_login),
  2866. 'ldap_login', (Controller, LDAP_login),
  2867. 'mssql_login', (Controller, MSSQL_login),
  2868. 'oracle_login', (Controller, Oracle_login),
  2869. 'mysql_login', (Controller, MySQL_login),
  2870. #'rdp_login', '',
  2871. 'pgsql_login', (Controller, Pgsql_login),
  2872. 'vnc_login', (Controller, VNC_login),
  2873.  
  2874. 'dns_reverse', (Controller_DNS, DNS_reverse),
  2875. 'dns_forward', (Controller_DNS, DNS_forward),
  2876. 'snmp_login', (Controller, SNMP_login),
  2877.  
  2878. 'unzip_pass', (Controller, Unzip_pass),
  2879. 'keystore_pass', (Controller, Keystore_pass),
  2880. )
  2881.  
  2882. module_deps = {
  2883. 'paramiko': [('ssh_login',), 'http://www.lag.net/paramiko/'],
  2884. 'pycurl': [('http_fuzz',), 'http://pycurl.sourceforge.net/'],
  2885. 'openldap': [('ldap_login',), 'http://www.openldap.org/'],
  2886. 'impacket': [('smb_login',), 'http://oss.coresecurity.com/projects/impacket.html'],
  2887. 'cx_Oracle': [('oracle_login',), 'http://cx-oracle.sourceforge.net/'],
  2888. 'mysql-python': [('mysql_login',), 'http://sourceforge.net/projects/mysql-python/'],
  2889. 'psycopg': [('pgsql_login',), 'http://initd.org/psycopg/'],
  2890. 'pycrypto': [('vnc_login',), 'http://www.dlitz.net/software/pycrypto/'],
  2891. 'pydns': [('dns_reverse', 'dns_forward'), 'http://pydns.sourceforge.net/'],
  2892. 'pysnmp': [('snmp_login',), 'http://pysnmp.sf.net/'],
  2893. 'unzip': [('unzip_pass',), 'http://www.info-zip.org/'],
  2894. 'java': [('keystore_pass',), 'http://www.oracle.com/technetwork/java/javase/'],
  2895. }
  2896. # }}}
  2897.  
  2898. # main {{{
  2899. if __name__ == '__main__':
  2900. from sys import argv
  2901. from os.path import basename
  2902.  
  2903. def show_usage():
  2904. print('''Usage:
  2905. $ ./patator.py module --help
  2906. or
  2907. $ ln -s patator.py module
  2908. $ ./module --help
  2909.  
  2910. Available modules:
  2911. %s''' % '\n'.join(' + %-13s : %s' % (k, v[1].__doc__) for k, v in modules))
  2912.  
  2913. exit(2)
  2914.  
  2915. # module name
  2916. modules = zip(modules[0::2], modules[1::2])
  2917. available = dict((k, v) for k, v in modules)
  2918.  
  2919. name = basename(argv[0]).lower()
  2920. if name not in available:
  2921. if len(argv) == 1:
  2922. show_usage()
  2923. name = basename(argv[1]).lower()
  2924. if name not in available:
  2925. show_usage()
  2926. argv = argv[1:]
  2927.  
  2928. # dependencies
  2929. abort = False
  2930. for w in warnings:
  2931. mods, url = module_deps[w]
  2932. if name in mods:
  2933. print('ERROR: %s (%s) is required to run %s.' % (w, url, name))
  2934. abort = True
  2935.  
  2936. if abort:
  2937. print('Please read the README inside for more information.')
  2938. exit(3)
  2939.  
  2940. # start
  2941. ctrl, module = available[name]
  2942. powder = ctrl(module, [name] + argv[1:])
  2943. powder.fire()
  2944.  
  2945. # }}}
  2946.  
  2947. # vim: ts=2 sw=2 sts=2 et fdm=marker
Add Comment
Please, Sign In to add comment