forked from lanjelot/patator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
patator.py
executable file
·5305 lines (4077 loc) · 163 KB
/
patator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# Copyright (C) 2012 Sebastien MACKE
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License version 2, as published by the
# Free Software Foundation
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details (http://www.gnu.org/licenses/gpl.txt).
import sys
__author__ = 'Sebastien Macke'
__email__ = '[email protected]'
__url__ = 'http://www.hsc.fr/ressources/outils/patator/'
__git__ = 'https://github.com/lanjelot/patator'
__twitter__ = 'https://twitter.com/lanjelot'
__version__ = '0.9'
__license__ = 'GPLv2'
__pyver__ = '%d.%d.%d' % sys.version_info[0:3]
__banner__ = 'Patator %s (%s) with python-%s' % (__version__, __git__, __pyver__)
# README {{{
'''
INTRODUCTION
------------
* What ?
Patator is a multi-purpose brute-forcer, with a modular design and a flexible usage.
Currently it supports the following modules:
+ ftp_login : Brute-force FTP
+ ssh_login : Brute-force SSH
+ telnet_login : Brute-force Telnet
+ smtp_login : Brute-force SMTP
+ smtp_vrfy : Enumerate valid users using SMTP VRFY
+ smtp_rcpt : Enumerate valid users using SMTP RCPT TO
+ finger_lookup : Enumerate valid users using Finger
+ http_fuzz : Brute-force HTTP
+ rdp_gateway : Brute-force RDP Gateway
+ ajp_fuzz : Brute-force AJP
+ pop_login : Brute-force POP3
+ pop_passd : Brute-force poppassd (http://netwinsite.com/poppassd/)
+ imap_login : Brute-force IMAP4
+ ldap_login : Brute-force LDAP
+ dcom_login : Brute-force DCOM
+ smb_login : Brute-force SMB
+ smb_lookupsid : Brute-force SMB SID-lookup
+ rlogin_login : Brute-force rlogin
+ vmauthd_login : Brute-force VMware Authentication Daemon
+ mssql_login : Brute-force MSSQL
+ oracle_login : Brute-force Oracle
+ mysql_login : Brute-force MySQL
+ mysql_query : Brute-force MySQL queries
* rdp_login : Brute-force RDP (NLA)
+ pgsql_login : Brute-force PostgreSQL
+ vnc_login : Brute-force VNC
+ dns_forward : Forward DNS lookup
+ dns_reverse : Reverse DNS lookup
+ snmp_login : Brute-force SNMP v1/2/3
+ ike_enum : Enumerate IKE transforms
+ unzip_pass : Brute-force the password of encrypted ZIP files
+ keystore_pass : Brute-force the password of Java keystore files
+ sqlcipher_pass : Brute-force the password of SQLCipher-encrypted databases
+ umbraco_crack : Crack Umbraco HMAC-SHA1 password hashes
+ tcp_fuzz : Fuzz TCP services
+ dummy_test : Testing module
Future modules to be implemented:
- rdp_login w/no NLA
The name "Patator" comes from https://www.youtube.com/watch?v=9sF9fTALhVA
* Why ?
Basically, I got tired of using Medusa, Hydra, Ncrack, Metasploit auxiliary modules, Nmap NSE scripts and the like because:
- they either do not work or are not reliable (got me false negatives several times in the past)
- they are not flexible enough (how to iterate over all wordlists, fuzz any module parameter)
- they lack useful features (display progress or pause during execution)
FEATURES
--------
* No false negatives, as it is the user that decides what results to ignore based on:
+ status code of response
+ size of response
+ matching string or regex in response data
+ ... see --help
* Modular design
+ not limited to network modules (eg. the unzip_pass module)
+ not limited to brute-forcing (eg. remote exploit testing, or vulnerable version probing)
* Interactive runtime
+ show progress during execution (press Enter)
+ pause/unpause execution (press p)
+ increase/decrease verbosity
+ add new actions & conditions during runtime (eg. to exclude more types of response from showing)
+ ... press h to see all available interactive commands
* Use persistent connections (ie. will test several passwords until the server disconnects)
* Multi-threaded
* Flexible user input
- Any module parameter can be fuzzed:
+ use the FILE keyword to iterate over a file
+ use the COMBO keyword to iterate over a combo file
+ use the NET keyword to iterate over every hosts of a network subnet
+ use the RANGE keyword to iterate over hexadecimal, decimal or alphabetical ranges
+ use the PROG keyword to iterate over the output of an external program
- Iteration over the joined wordlists can be done in any order
* Save every response (along with request) to separate log files for later reviewing
INSTALL
-------
* Dependencies (best tested versions)
| Required for | URL | Version |
--------------------------------------------------------------------------------------------------
paramiko | SSH | http://www.lag.net/paramiko/ | 2.7.1 |
--------------------------------------------------------------------------------------------------
pycurl | HTTP | http://pycurl.sourceforge.net/ | 7.43.0 |
--------------------------------------------------------------------------------------------------
libcurl | HTTP | https://curl.haxx.se/ | 7.58.0 |
--------------------------------------------------------------------------------------------------
ajpy | AJP | https://github.com/hypn0s/AJPy/ | 0.0.4 |
--------------------------------------------------------------------------------------------------
openldap | LDAP | http://www.openldap.org/ | 2.4.45 |
--------------------------------------------------------------------------------------------------
impacket | SMB, MSSQL | https://github.com/CoreSecurity/impacket | 0.9.20 |
--------------------------------------------------------------------------------------------------
pyOpenSSL | impacket | https://pyopenssl.org/ | 19.1.0 |
--------------------------------------------------------------------------------------------------
cx_Oracle | Oracle | http://cx-oracle.sourceforge.net/ | 7.3.0 |
--------------------------------------------------------------------------------------------------
mysqlclient | MySQL | https://github.com/PyMySQL/mysqlclient-python | 1.4.6 |
--------------------------------------------------------------------------------------------------
xfreerdp | RDP (NLA) | https://github.com/FreeRDP/FreeRDP/ | 1.2.0 |
--------------------------------------------------------------------------------------------------
psycopg | PostgreSQL | http://initd.org/psycopg/ | 2.8.4 |
--------------------------------------------------------------------------------------------------
pycrypto | VNC, impacket | http://www.dlitz.net/software/pycrypto/ | 2.6.1 |
--------------------------------------------------------------------------------------------------
dnspython | DNS | http://www.dnspython.org/ | 1.16.0 |
--------------------------------------------------------------------------------------------------
IPy | NET keyword | https://github.com/haypo/python-ipy | 1.0 |
--------------------------------------------------------------------------------------------------
pysnmp | SNMP | http://pysnmp.sourceforge.net/ | 4.4.12 |
--------------------------------------------------------------------------------------------------
pyasn1 | SNMP, impacket | http://sourceforge.net/projects/pyasn1/ | 0.4.8 |
--------------------------------------------------------------------------------------------------
ike-scan | IKE | http://www.nta-monitor.com/tools-resources/ | 1.9 |
--------------------------------------------------------------------------------------------------
unzip | ZIP passwords | http://www.info-zip.org/ | 6.0 |
--------------------------------------------------------------------------------------------------
Java | keystore files | http://www.oracle.com/technetwork/java/javase/ | 6 |
--------------------------------------------------------------------------------------------------
pysqlcipher3 | SQLCipher | https://github.com/rigglemania/pysqlcipher3 | 1.0.3 |
--------------------------------------------------------------------------------------------------
python | | http://www.python.org/ | 3.6 |
--------------------------------------------------------------------------------------------------
* Shortcuts (optional)
ln -s path/to/patator.py /usr/bin/ftp_login
ln -s path/to/patator.py /usr/bin/http_fuzz
etc.
USAGE
-----
$ python patator.py <module> -h # or
$ <module> -h # if shortcuts were created
There are global options and module options:
- all global options start with - or --
- all module options are of the form option=value
All module options are fuzzable:
---------
./module host=FILE0 port=FILE1 foobar=FILE2.google.FILE3 0=hosts.txt 1=ports.txt 2=foo.txt 3=bar.txt
If a module option starts with the @ character, data will be loaded from the given filename.
$ ./http_fuzz [email protected] 0=vhosts.txt 1=uagents.txt
The keywords (FILE, COMBO, NET, ...) act as place-holders. They indicate the type of wordlist
and where to replace themselves with the actual words to test.
Each keyword is numbered in order to:
- match the corresponding wordlist
- and indicate in what order to iterate over all the wordlists
For example, this would be the classic order:
---------
$ ./module host=FILE0 user=FILE1 password=FILE2 0=hosts.txt 1=logins.txt 2=passwords.txt
10.0.0.1 root password
10.0.0.1 root 123456
10.0.0.1 root qsdfghj
... (trying all passwords before testing next login)
10.0.0.1 admin password
10.0.0.1 admin 123456
10.0.0.1 admin qsdfghj
... (trying all logins before testing next host)
10.0.0.2 root password
...
While a more effective order might be:
---------
$ ./module host=FILE2 user=FILE1 password=FILE0 2=hosts.txt 1=logins.txt 0=passwords.txt
10.0.0.1 root password
10.0.0.2 root password
10.0.0.1 admin password
10.0.0.2 admin password
10.0.0.1 root 123456
10.0.0.2 root 123456
10.0.0.1 admin 123456
...
By default Patator iterates over the cartesian product of all payload sets. Use
the --groups option to iterate over sets simultaneously instead. For example to
distribute all payloads among identical servers:
---------
$ ./module name=FILE0.FILE1 resolver=FILE2 0=names.txt 1=domains.txt 2=ips.txt --groups 0,1:2
ftp.abc.fr 8.8.8.8
ftp.xyz.fr 8.8.4.4
git.abc.fr 8.8.8.8
git.xyz.fr 8.8.4.4
www.abc.fr 8.8.8.8
www.xyz.fr 8.8.4.4
The numbers of every keyword given on the command line must be specified.
Use ',' to iterate over the cartesian product of sets and use ':' to iterate
over sets simultaneously.
* Keywords
Brute-force a list of hosts with a file containing combo entries (each line => login:password).
---------
./module host=FILE0 user=COMBO10 password=COMBO11 0=hosts.txt 1=combos.txt
Scan subnets to just grab version banners.
---------
./module host=NET0 0=10.0.1.0/24,10.0.2.0/24,10.0.3.128-10.0.3.255
Fuzz a parameter by iterating over a range of values.
---------
./module param=RANGE0 0=hex:0x00-0xffff
./module param=RANGE0 0=int:0-500
./module param=RANGE0 0=lower:a-zzz
Fuzz a parameter by iterating over the output of an external program.
---------
./module param=PROG0 0='john -stdout -i'
./module param=PROG0 0='mp64.bin ?l?l?l',$(mp64.bin --combination ?l?l?l) # http://hashcat.net/wiki/doku.php?id=maskprocessor
* Actions & Conditions
Use the -x option to do specific actions upon receiving specific responses. For example:
Ignore responses with status code 200 *AND* a size within a specific range.
---------
./module host=10.0.0.1 user=FILE0 -x ignore:code=200,size=57-74
Ignore responses with status code 500 *OR* containing "Internal error".
---------
./module host=10.0.0.1 user=FILE0 -x ignore:code=500 -x ignore:fgrep='Internal error'
Remember that conditions are ANDed within the same -x option, use multiple -x options to
specify ORed conditions.
* Actions skip and free
Stop testing the same value from keyword #0 after a valid combination is found.
---------
./module data=FILE0.FILE1 -x skip=0:fgrep=Success
Stop testing the same combination after a valid match is found.
---------
./module data=FILE0.FILE1 data2=RANGE2 -x free=data:fgrep=Success
* Failures
During execution, failures may happen, such as a TCP connect timeout for
example. By definition a failure is an exception that the module does not expect,
and as a result the exception is caught upstream by the controller.
Such exceptions, or failures, are not immediately reported to the user, the
controller will retry 4 more times (see --max-retries) before reporting the
failed payload to the user with the logging level "FAIL".
* Read carefully the following examples to get a good understanding of how patator works.
{{{ FTP
* Brute-force authentication. Do not report wrong passwords.
---------
$ ftp_login host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt -x ignore:mesg='Login incorrect.'
NB0. If you get errors like "500 OOPS: priv_sock_get_cmd", use -x ignore,reset,retry:code=500
in order to retry the last login/password using a new TCP connection. Odd servers like vsftpd
return this when they shut down the TCP connection (ie. max login attempts reached).
NB1. If you get errors like "too many connections from your IP address", try decreasing the number of
threads, the server may be enforcing a maximum number of concurrent connections.
* Same as before, but stop testing a user after his password is found.
---------
$ ftp_login ... -x free=user:code=0
* Find anonymous FTP servers on a subnet.
---------
$ ftp_login host=NET0 user=anonymous [email protected] 0=10.0.0.0/24
}}}
{{{ SSH
* Brute-force authentication with password same as login (aka single mode). Do not report wrong passwords.
---------
$ ssh_login host=10.0.0.1 user=FILE0 password=FILE0 0=logins.txt -x ignore:mesg='Authentication failed.'
NB. If you get errors like "Error reading SSH protocol banner ... Connection reset by peer",
try decreasing the number of threads, the server may be enforcing a maximum
number of concurrent connections (eg. MaxStartups in OpenSSH).
* Brute-force several hosts and stop testing a host after a valid password is found.
---------
$ ssh_login host=FILE0 user=FILE1 password=FILE2 0=hosts.txt 1=logins.txt 2=passwords.txt -x free=host:code=0
* Same as previous, but stop testing a user on a host after his password is found.
---------
$ ssh_login host=FILE0 user=FILE1 password=FILE2 0=hosts.txt 1=logins.txt 2=passwords.txt -x free=host+user:code=0
}}}
{{{ Telnet
* Brute-force authentication.
(a) Enter login after first prompt is detected, enter password after second prompt.
(b) The regex to detect the login and password prompts.
(c) Reconnect when we get no login prompt back (max number of tries reached or successful login).
------------ (a)
$ telnet_login host=10.0.0.1 inputs='FILE0\nFILE1' 0=logins.txt 1=passwords.txt \
prompt_re='tux login:|Password:' -x reset:egrep!='Login incorrect.+tux login:'
(b) (c)
NB. If you get errors like "telnet connection closed", try decreasing the number of threads,
the server may be enforcing a maximum number of concurrent connections.
}}}
{{{ SMTP
* Enumerate valid users using the VRFY command.
(a) Do not report invalid recipients.
(b) Do not report when the server shuts us down with "421 too many errors", reconnect and resume testing.
--------- (a)
$ smtp_vrfy host=10.0.0.1 user=FILE0 0=logins.txt -x ignore:fgrep='User unknown in local recipient table' \
-x ignore,reset,retry:code=421
(b)
* Use the RCPT TO command in case the VRFY command is not available.
---------
$ smtp_rcpt host=10.0.0.1 user=FILE0@localhost 0=logins.txt helo='ehlo mx.fb.com' mail_from=root
* Brute-force authentication.
(a) Send a fake hostname (by default your host fqdn is sent)
--------- (a)
$ smtp_login host=10.0.0.1 helo='ehlo its.me.com' [email protected] password=FILE1 0=logins.txt 1=passwords.txt
}}}
{{{ HTTP
* Find hidden web resources.
(a) Use a specific header.
(b) Follow redirects.
(c) Do not report 404 errors.
(d) Retry on 500 errors.
--------- (a)
$ http_fuzz url=http://localhost/FILE0 0=words.txt header='Cookie: SESSID=A2FD8B2DA4' \
follow=1 -x ignore:code=404 -x ignore,retry:code=500
(b) (c) (d)
NB. You may be able to go 10 times faster using webef (http://www.hsc.fr/ressources/outils/webef/).
It is the fastest HTTP brute-forcer I know, yet at the moment it still lacks useful features
that will prevent you from performing the following attacks.
* Brute-force phpMyAdmin logon.
(a) Use POST requests.
(b) Follow redirects using cookies sent by server.
(c) Ignore failed authentications.
--------- (a) (b) (b)
$ http_fuzz url=http://10.0.0.1/phpmyadmin/index.php method=POST follow=1 accept_cookie=1 \
body='pma_username=root&pma_password=FILE0&server=1&lang=en' 0=passwords.txt \
-x ignore:fgrep='Cannot log in to the MySQL server'
(c)
* Scan subnet for directory listings.
(a) Ignore not matching responses.
(b) Save matching responses into directory.
---------
$ http_fuzz url=http://NET0/FILE1 0=10.0.0.0/24 1=dirs.txt -x ignore:fgrep!='Index of' \
-l /tmp/directory-listings (a)
(b)
* Brute-force Basic authentication.
(a) Single mode (login == password).
(b) Do not report failed login attempts.
---------
$ http_fuzz url=http://10.0.0.1/manager/html user_pass=FILE0:FILE0 0=logins.txt -x ignore:code=401
(a) (b)
* Find hidden virtual hosts.
(a) Read template from file.
(b) Fuzz both the Host and User-Agent headers.
(c) Stop testing a virtual host name after a valid one is found.
---------
$ echo -e 'Host: FILE0\nUser-Agent: FILE1' > headers.txt
$ http_fuzz url=http://10.0.0.1/ [email protected] 0=vhosts.txt 1=agents.txt -x skip=0:code!=404
(a) (b) (c)
* Brute-force logon using GET requests.
(a) Encode everything surrounded by the two tags _@@_ in hexadecimal.
(b) Ignore HTTP 200 responses with a content size (header+body) within given range
and that also contain the given string.
(c) Use a different delimiter string because the comma cannot be escaped.
--------- (a)
$ http_fuzz url='http://10.0.0.1/login?username=admin&password=_@@_FILE0_@@_' -e _@@_:hex \
0=words.txt -x ignore:'code=200|size=1500-|fgrep=Welcome, unauthenticated user' -X '|'
(b) (c)
* Brute-force logon that enforces two random nonces to be submitted along every POST.
(a) First, request the page that provides the nonces as hidden input fields.
(b) Use regular expressions to extract the nonces that are to be submitted along the main request.
---------
$ http_fuzz url=http://10.0.0.1/login method=POST body='user=admin&pass=FILE0&nonce1=_N1_&nonce2=_N2_' 0=passwords.txt accept_cookie=1 \
before_urls=http://10.0.0.1/index before_egrep='_N1_:<input type="hidden" name="nonce1" value="(\w+)"|_N2_:name="nonce2" value="(\w+)"'
(a) (b)
* Test the OPTIONS method against a list of URLs.
(a) Ignore URLs that only allow the HEAD and GET methods.
(b) Header end of line is '\r\n'.
(c) Use a different delimiter string because the comma cannot be escaped.
---------
$ http_fuzz url=FILE0 0=urls.txt method=OPTIONS -x ignore:egrep='^Allow: HEAD, GET\r$' -X '|'
(a) (b) (c)
}}}
{{{ LDAP
* Brute-force authentication.
(a) Do not report wrong passwords.
(b) Talk SSL/TLS to port 636.
---------
$ ldap_login host=10.0.0.1 binddn='cn=FILE0,dc=example,dc=com' 0=logins.txt bindpw=FILE1 1=passwords.txt \
-x ignore:mesg='ldap_bind: Invalid credentials (49)' ssl=1 port=636
(a) (b)
}}}
{{{ SMB
* Brute-force authentication.
---------
$ smb_login host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt -x ignore:fgrep=STATUS_LOGON_FAILURE
NB. If you suddenly get STATUS_ACCOUNT_LOCKED_OUT errors for an account although
it is not the first password you test on this account, then you must have locked it.
* Pass-the-hash.
(a) Test a list of hosts.
(b) Test every user (each line := login:rid:LM hash:NT hash).
---------
$ smb_login host=FILE0 0=hosts.txt user=COMBO10 password_hash=COMBO12:COMBO13 1=pwdump.txt -x ...
(a) (b)
}}}
{{{ rlogin
* Brute-force usernames that root might be allowed to login as with no password (eg. a ~/.rhosts file with the line "+ root").
$ rlogin_login host=10.0.0.1 luser=root user=FILE0 0=logins.txt persistent=0 -x ignore:fgrep=Password:
* Brute-force usernames that might be allowed to login as root with no password (eg. a /root/.rhosts file with the line "+ john").
$ rlogin_login host=10.0.0.1 user=root luser=FILE0 0=logins.txt persistent=0 -x ignore:fgrep=Password:
}}}
{{{ MSSQL
* Brute-force authentication.
-----------
$ mssql_login host=10.0.0.1 user=sa password=FILE0 0=passwords.txt -x ignore:fgrep='Login failed for user'
}}}
{{{ Oracle
Beware, by default in Oracle, accounts are permanently locked out after 10 wrong passwords,
except for the SYS account.
* Brute-force authentication.
------------
$ oracle_login host=10.0.0.1 user=SYS password=FILE0 0=passwords.txt sid=ORCL -x ignore:code=ORA-01017
NB0. With Oracle 10g XE (Express Edition), you do not need to pass a SID.
NB1. If you get ORA-12516 errors, it may be because you reached the limit of
concurrent connections or db processes, try using "--rate-limit 0.5 -t 2" to be
more polite. Also you can run "alter system set processes=150 scope=spfile;"
and restart your database to get rid of this.
* Brute-force SID.
------------
$ oracle_login host=10.0.0.1 sid=FILE0 0=sids.txt -x ignore:code=ORA-12505
NB. Against Oracle9, it may crash (Segmentation fault) as soon as a valid SID is
found (cx_Oracle bug). Sometimes, the SID gets printed out before the crash,
so try running the same command again if it did not.
}}}
{{{ MySQL
* Brute-force authentication.
-----------
$ mysql_login host=10.0.0.1 user=FILE0 password=FILE0 0=logins.txt -x ignore:fgrep='Access denied for user'
}}}
{{{ PostgreSQL
* Brute-force authentication.
-----------
$ pgsql_login host=10.0.0.1 user=postgres password=FILE0 0=passwords.txt -x ignore:fgrep='password authentication failed'
}}}
{{{ VNC
Some VNC servers have built-in anti-bruteforce functionality that temporarily
blacklists the attacker IP address after too many wrong passwords.
- RealVNC-4.1.3 or TightVNC-1.3.10 for example, allow 5 failed attempts and
then enforce a 10 second delay. For each subsequent failed attempt that
delay is doubled.
- RealVNC-3.3.7 or UltraVNC allow 6 failed attempts and then enforce a 10
second delay between each following attempt.
* Brute-force authentication.
(a) No need to use more than one thread.
(b) Keep retrying the same password when we are blacklisted by the server.
(c) Exit execution as soon as a valid password is found.
--------- (a)
$ vnc_login host=10.0.0.1 password=FILE0 0=passwords.txt --threads 1 \
-x retry:fgrep!='Authentication failure' --max-retries -1 -x quit:code=0
(b) (b) (c)
}}}
{{{ DNS
* Brute-force subdomains.
(a) Ignore NXDOMAIN responses (rcode 3).
-----------
$ dns_forward name=FILE0.google.com 0=names.txt -x ignore:code=3
(a)
* Brute-force domain with every possible TLDs.
-----------
$ dns_forward name=google.MOD0 0=TLD -x ignore:code=3
* Brute-force SRV records.
-----------
$ dns_forward name=MOD0.microsoft.com 0=SRV qtype=SRV -x ignore:code=3
* Grab the version of several hosts.
-----------
$ dns_forward server=FILE0 0=hosts.txt name=version.bind qtype=txt qclass=ch
* Reverse lookup several networks.
(a) Ignore names that do not contain 'google.com'.
(b) Ignore generic PTR records.
-----------
$ 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-
(a) (b)
}}}
{{{ SNMP
* SNMPv1/2 : Find valid community names.
----------
$ snmp_login host=10.0.0.1 community=FILE0 0=names.txt -x ignore:mesg='No SNMP response received before timeout'
* SNMPv3 : Find valid usernames.
----------
$ snmp_login host=10.0.0.1 version=3 user=FILE0 0=logins.txt -x ignore:mesg=unknownUserName
* SNMPv3 : Find valid passwords.
----------
$ snmp_login host=10.0.0.1 version=3 user=myuser auth_key=FILE0 0=passwords.txt -x ignore:mesg=wrongDigest
NB0. If you get "notInTimeWindow" error messages, increase the retries option.
NB1. SNMPv3 requires passphrases to be at least 8 characters long.
}}}
{{{ Unzip
* Brute-force the ZIP file password (cracking older pkzip encryption used to be not supported in JtR).
----------
$ unzip_pass zipfile=file.zip password=FILE0 0=passwords.txt -x ignore:code!=0
}}}
CHANGELOG
---------
* v0.9 2020/07/26
- fixed encoding bugs
- new Dockerfile
- new --groups and --auto-progress options
- fixed various issues reported on Github
- new testing env with docker-compose
* v0.8 2020/03/22
- new switches (-R, --csv, --xml, --hits)
- new pathasis option for http_fuzz
- new rdp_gateway module
- fixed various issues reported on Github
* v0.7 2017/12/14
- added Python3 support
- added Windows support
- new --timeout and --allow-ignore-failures options
- switched to multiprocesses instead of threads (for --timeout to work on Windows)
- new modules: ike_enum, rdp_login, ajp_fuzz, sqlcipher_pass
- more info added to XML output
- fixed many bugs
* v0.6 2014/08/25
- added CSV and XML output formats
- added module execution time column
- improved RANGE keyword
- new modules: rlogin_login, umbrack_crack
- minor bug fixes/improvements in http_fuzz and smb_login
- added more TLDs to dns_forward
* v0.5 2013/07/05
- new modules: mysql_query, tcp_fuzz
- new RANGE and PROG keywords (supersedes the reading from stdin feature)
- switched to impacket for mssql_login
- output more intuitive
- fixed connection cache
- minor bug fixes
* v0.4 2012/11/02
- new modules: smb_lookupsid, finger_lookup, pop_login, imap_login, vmauthd_login
- improved connection cache
- improved usage, user can now act upon specific responses (eg. stop brute-forcing host if down, or stop testing login if password found)
- improved dns brute-forcing presentation
- switched to dnspython which is not limited to the IN class (eg. can now scan for {hostname,version}.bind)
- rewrote itertools.product to avoid memory over-consumption when using large wordlists
- can now read wordlist from stdin
- added timeout option to most of the network brute-forcing modules
- added SSL and/or TLS support to a few modules
- before_egrep now allows more than one expression (ie. useful when more than one random nonce needs to be submitted)
- fixed numerous bugs
* v0.3 2011/12/16
- minor bugs fixed in http_fuzz
- option -e better implemented
- better warnings about missing dependencies
* v0.2 2011/12/01
- new smtp_login module
- several bugs fixed
* v0.1 2011/11/25 : Public release
TODO
----
* new option -e ns like in Medusa (not likely to be implemented due to design)
* replace dnspython|paramiko|IPy with a better module (scapy|libssh2|netaddr... ?) // https://netaddr.readthedocs.org/en/latest/tutorial_01.html
'''
# }}}
# logging {{{
class Logger:
def __init__(self, queue):
self.queue = queue
self.name = multiprocessing.current_process().name
def send(self, action, *args):
self.queue.put((self.name, action, args))
def quit(self):
self.send('quit')
def headers(self):
self.send('headers')
def result(self, *args):
self.send('result', *args)
def save_response(self, *args):
self.send('save_response', *args)
def save_hit(self, *args):
self.send('save_hit', *args)
def setLevel(self, level):
self.send('setLevel', level)
def warn(self, msg):
self.send('warning', msg)
def info(self, msg):
self.send('info', msg)
def debug(self, msg):
self.send('debug', msg)
import logging
class TXTFormatter(logging.Formatter):
def __init__(self, indicatorsfmt):
self.resultfmt = '%(asctime)s %(name)-7s %(levelname)7s - ' + ' '.join('%%(%s)%ss' % (k, v) for k, v in indicatorsfmt) + ' | %(candidate)-34s | %(num)5s | %(mesg)s'
super(TXTFormatter, self).__init__(datefmt='%H:%M:%S')
def format(self, record):
if not record.msg or record.msg == 'headers':
fmt = self.resultfmt
else:
if record.levelno == logging.DEBUG:
fmt = '%(asctime)s %(name)-7s %(levelname)7s [%(pname)s] %(message)s'
else:
fmt = '%(asctime)s %(name)-7s %(levelname)7s - %(message)s'
if PY3:
self._style._fmt = fmt
else:
self._fmt = fmt
pp = {}
for k, v in record.__dict__.items():
if k in ['candidate', 'mesg']:
pp[k] = repr23(v)
else:
pp[k] = v
return super(TXTFormatter, self).format(logging.makeLogRecord(pp))
class CSVFormatter(logging.Formatter):
def __init__(self, indicatorsfmt):
fmt = '%(asctime)s,%(levelname)s,'+','.join('%%(%s)s' % name for name, _ in indicatorsfmt)+',%(candidate)s,%(num)s,%(mesg)s'
super(CSVFormatter, self).__init__(fmt=fmt, datefmt='%H:%M:%S')
def format(self, record):
pp = {}
for k, v in record.__dict__.items():
if k in ['candidate', 'mesg']:
pp[k] = '"%s"' % v.replace('"', '""')
else:
pp[k] = v
return super(CSVFormatter, self).format(logging.makeLogRecord(pp))
class XMLFormatter(logging.Formatter):
def __init__(self, indicatorsfmt):
fmt = '''<result time="%(asctime)s" level="%(levelname)s">
''' + '\n'.join(' <{0}>%({1})s</{0}>'.format(name.replace(':', '_'), name) for name, _ in indicatorsfmt) + '''
<candidate>%(candidate)s</candidate>
<num>%(num)s</num>
<mesg>%(mesg)s</mesg>
<target %(target)s/>
</result>'''
super(XMLFormatter, self).__init__(fmt=fmt, datefmt='%H:%M:%S')
def format(self, record):
pp = {}
for k, v in record.__dict__.items():
if isinstance(v, str):
pp[k] = xmlescape(v)
else:
pp[k] = v
return super(XMLFormatter, self).format(logging.makeLogRecord(pp))
class MsgFilter(logging.Filter):
def filter(self, record):
if record.msg:
return 0
else:
return 1
def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xml_file, hits_file):
ignore_ctrlc()
if PY3:
logging._levelToName[logging.ERROR] = 'FAIL'
encoding = 'latin1'
else:
logging._levelNames[logging.ERROR] = 'FAIL'
encoding = None
handler_out = logging.StreamHandler()
handler_out.setFormatter(TXTFormatter(indicatorsfmt))
logger = logging.getLogger('patator')
logger.setLevel(logging.DEBUG)
logger.addHandler(handler_out)
names = [name for name, _ in indicatorsfmt] + ['candidate', 'num', 'mesg']
if runtime_file or log_dir:
runtime_log = os.path.join(log_dir or '', runtime_file or 'RUNTIME.log')
with open(runtime_log, 'a') as f:
f.write('$ %s\n' % ' '.join(argv))
handler_log = logging.FileHandler(runtime_log, encoding=encoding)
handler_log.setFormatter(TXTFormatter(indicatorsfmt))
logger.addHandler(handler_log)
if csv_file or log_dir:
results_csv = os.path.join(log_dir or '', csv_file or 'RESULTS.csv')
if not os.path.exists(results_csv):
with open(results_csv, 'w') as f:
f.write('time,level,%s\n' % ','.join(names))
handler_csv = logging.FileHandler(results_csv, encoding=encoding)
handler_csv.addFilter(MsgFilter())
handler_csv.setFormatter(CSVFormatter(indicatorsfmt))
logger.addHandler(handler_csv)
if xml_file or log_dir:
results_xml = os.path.join(log_dir or '', xml_file or 'RESULTS.xml')
if not os.path.exists(results_xml):
with open(results_xml, 'w') as f:
f.write('<?xml version="1.0" encoding="UTF-8"?>\n<root>\n')
f.write('<start utc=%s local=%s/>\n' % (xmlquoteattr(strfutctime()), xmlquoteattr(strflocaltime())))
f.write('<cmdline>%s</cmdline>\n' % xmlescape(' '.join(argv)))
f.write('<module>%s</module>\n' % xmlescape(argv[0]))
f.write('<options>\n')
i = 0
del argv[0]
while i < len(argv):
arg = argv[i]
if arg[0] == '-':
if arg in ('-d', '--debug', '--allow-ignore-failures', '-y'):
f.write(' <option type="global" name=%s/>\n' % xmlquoteattr(arg))
else:
if not arg.startswith('--') and len(arg) > 2:
name, value = arg[:2], arg[2:]
elif '=' in arg:
name, value = arg.split('=', 1)
else:
name, value = arg, argv[i+1]
i += 1
f.write(' <option type="global" name=%s>%s</option>\n' % (xmlquoteattr(name), xmlescape(value)))
else:
name, value = arg.split('=', 1)
f.write(' <option type="module" name=%s>%s</option>\n' % (xmlquoteattr(name), xmlescape(value)))
i += 1
f.write('</options>\n')
f.write('<results>\n')
else: # remove "</results>...</root>"
with open(results_xml, 'r+b') as f:
offset = f.read().find(b'</results>')
if offset != -1:
f.seek(offset)
f.truncate(f.tell())
handler_xml = logging.FileHandler(results_xml, encoding=encoding)
handler_xml.addFilter(MsgFilter())
handler_xml.setFormatter(XMLFormatter(indicatorsfmt))
logger.addHandler(handler_xml)
if hits_file:
if os.path.exists(hits_file):
os.rename(hits_file, hits_file + '.' + strftime("%Y%m%d%H%M%S", localtime()))
while True:
pname, action, args = queue.get()
if action == 'quit':
if log_dir:
with open(os.path.join(log_dir, 'RESULTS.xml'), 'a') as f:
f.write('</results>\n<stop utc=%s local=%s/>\n</root>\n' % (xmlquoteattr(strfutctime()), xmlquoteattr(strflocaltime())))
break
elif action == 'headers':
logger.info(' '*77)
logger.info('headers', extra=dict((n, n) for n in names))
logger.info('-'*77)
elif action == 'result':
typ, resp, candidate, num = args
results = [(name, value) for (name, _), value in zip(indicatorsfmt, resp.indicators())]
results += [('candidate', candidate), ('num', num), ('mesg', str(resp)), ('target', resp.str_target())]
if typ == 'fail':
logger.error(None, extra=dict(results))
else:
logger.info(None, extra=dict(results))
elif action == 'save_response':
resp, num = args
if log_dir:
filename = '%d_%s' % (num, '-'.join(map(str, resp.indicators())))
with open('%s.txt' % os.path.join(log_dir, filename), 'wb') as f:
f.write(resp.dump())
elif action == 'save_hit':
if hits_file:
with open(hits_file, 'ab') as f:
f.write(b(args[0] +'\n'))
elif action == 'setLevel':
logger.setLevel(args[0])
else: # 'warn', 'info', 'debug'
getattr(logger, action)(args[0], extra={'pname': pname})
# }}}
# imports {{{
import re
import os
import sys
from time import localtime, gmtime, strftime, sleep, time
from platform import system
from functools import reduce
from operator import mul, itemgetter
from select import select
from itertools import islice, cycle
import string
import random
from decimal import Decimal
from base64 import b64encode
from datetime import timedelta, datetime
import socket
import subprocess
import hashlib
from collections import defaultdict
import multiprocessing
import signal
import ctypes
import glob
from xml.sax.saxutils import escape as xmlescape, quoteattr as xmlquoteattr
from ssl import wrap_socket
from binascii import hexlify, unhexlify
PY3 = sys.version_info >= (3,)
if PY3:
from queue import Empty, Full
from urllib.parse import quote, urlencode, urlparse, urlunparse, quote_plus, unquote
from io import StringIO
from sys import maxsize as maxint
else:
from Queue import Empty, Full
from urllib import quote, urlencode, quote_plus, unquote
from urlparse import urlparse, urlunparse
from cStringIO import StringIO
from sys import maxint
if PY3: # http://python3porting.com/problems.html
def b(x):
if isinstance(x, bytes):
return x
else:
return x.encode('ISO-8859-1', errors='ignore')
def B(x):
if isinstance(x, str):
return x
else:
return x.decode('ISO-8859-1', errors='ignore')
else: