|
15 | 15 |
|
16 | 16 | from . _dmt_report import resultParser |
17 | 17 | from core.vmnf_shared_args import VimanaSharedArgs |
18 | | -from core.vmnf_thread_handler import ThreadPool |
19 | | -from core.vmnf_thread_handler import Worker |
20 | | -from core.vmnf_thread_handler import ThreadPool |
21 | 18 |
|
22 | 19 | from resources.session.vmnf_sessions import createSession |
23 | 20 | from resources.vmnf_validators import get_tool_scope |
|
30 | 27 | from pygments.lexers import PythonLexer |
31 | 28 | from pygments import highlight |
32 | 29 |
|
| 30 | +import concurrent.futures |
33 | 31 | import sys, re, os, random, string, platform |
34 | 32 | from lxml.html.soupparser import fromstring |
35 | 33 | from termcolor import colored, cprint |
@@ -96,12 +94,23 @@ class siddhi: |
96 | 94 | def __init__(self, **vmnf_handler): |
97 | 95 |
|
98 | 96 | self.vmnf_handler = vmnf_handler |
| 97 | + self.wait = 0.02 |
| 98 | + if vmnf_handler['wait']: |
| 99 | + try: |
| 100 | + self.wait = int(vmnf_handler['wait'].strip()) |
| 101 | + except ValueError: |
| 102 | + self.wait = float(vmnf_handler['wait'].strip()) |
| 103 | + |
| 104 | + self.auto_mode = vmnf_handler['auto'] |
99 | 105 | self.threads = vmnf_handler['threads'] |
100 | 106 | self.debug = vmnf_handler['debug'] |
101 | | - self.auto_mode = vmnf_handler['auto'] |
102 | | - self.num_threads = self.threads if self.threads <= 10 else 10 |
| 107 | + self.timeout = vmnf_handler['timeout'] |
| 108 | + self.max_workers = 5 |
| 109 | + self.num_threads = self.threads \ |
| 110 | + if self.threads <= self.max_workers \ |
| 111 | + else self.max_workers |
| 112 | + |
103 | 113 | self.fuzz_mode = False |
104 | | - self.pool = ThreadPool(self.num_threads) |
105 | 114 |
|
106 | 115 | # root URL patterns |
107 | 116 | self.xlp_tbl = PrettyTable() |
@@ -432,8 +441,7 @@ def djmimic(self): |
432 | 441 | print(colored(" url(r'^{0}',\n views.{1},\n name='{2}'),".format( |
433 | 442 | url, view, name), 'white') |
434 | 443 | ) |
435 | | - sleep(0.07) |
436 | | - |
| 444 | + sleep(self.wait) |
437 | 445 | print("\n]") |
438 | 446 | #sleep(1) |
439 | 447 |
|
@@ -464,44 +472,241 @@ def expand_UP(self, up_fuzz_scope=False): |
464 | 472 |
|
465 | 473 | url_model = {} |
466 | 474 | trick_name = colors.bn_c + 'NoReverseMatch' + colors.D_c |
| 475 | + |
| 476 | + with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor: |
| 477 | + for x_pattern in self.root_url_patterns: |
| 478 | + if x_pattern.endswith('/'): |
| 479 | + x_pattern = x_pattern[:-1].strip() |
| 480 | + self.x_pattern = x_pattern |
| 481 | + |
| 482 | + app_name = ("app_name = '" + x_pattern + "'") |
| 483 | + expanded_pattern = '{}/{}/_._x'.format(self.dmt_start_base_r, x_pattern) |
| 484 | + |
| 485 | + sys.stdout.write("\r{0} Mapping URL pattern via {1} ({2}/{3}) -> {4}".format( |
| 486 | + colors.Gn_c + "⠿⠥" + colors.C_c, |
| 487 | + trick_name, |
| 488 | + up_c, n_up, |
| 489 | + colors.Y_c + x_pattern + colors.D_c, |
| 490 | + colors.Y_c + app_name + colors.D_c |
| 491 | + ) |
| 492 | + ) |
| 493 | + sys.stdout.flush() |
| 494 | + sleep(self.wait) |
467 | 495 |
|
468 | | - for x_pattern in self.root_url_patterns: |
469 | | - self.x_pattern = x_pattern |
470 | | - if x_pattern.endswith('/'): |
471 | | - x_pattern = x_pattern[:-1].strip() |
| 496 | + self.vmnf_handler['target_url'] = expanded_pattern |
| 497 | + future = executor.submit(createSession, **self.vmnf_handler) |
| 498 | + up_c += 1 |
472 | 499 |
|
473 | | - app_name = ("app_name = '" + x_pattern + "'") |
474 | | - expanded_pattern = '{}/{}/_._x'.format(self.dmt_start_base_r, x_pattern) |
475 | | - |
476 | | - sys.stdout.write("\r{0} Mapping URL pattern via {1} ({2}/{3}) -> {4}".format( |
477 | | - colors.Gn_c + "⠿⠥" + colors.C_c, |
478 | | - trick_name, |
479 | | - up_c, n_up, |
480 | | - colors.Y_c + x_pattern + colors.D_c, |
481 | | - colors.Y_c + app_name + colors.D_c |
482 | | - ) |
| 500 | + response = (future.result()) |
| 501 | + self.expanded_response = self.get_unescape_html(response.text) |
| 502 | + response_status = response.status_code |
| 503 | + |
| 504 | + if self.expanded_response: |
| 505 | + if response_status == 404: |
| 506 | + self.get_url_patterns() |
| 507 | + if self.vmnf_handler['debug']: |
| 508 | + self.djmimic() |
| 509 | + elif response_status == 500: |
| 510 | + self.dxt_parser(self.expanded_response, False, True) |
| 511 | + |
| 512 | + print() |
| 513 | + print(self.xlp_tbl_x) |
| 514 | + |
| 515 | + return self.expanded_patterns |
| 516 | + |
| 517 | + def parse_args(self): |
| 518 | + ''' ~ siddhi needs only shared arguments from VimanaSharedArgs() ~''' |
| 519 | + parser = argparse.ArgumentParser( |
| 520 | + add_help=False, |
| 521 | + parents=[VimanaSharedArgs().args()] |
| 522 | + ) |
| 523 | + return parser |
| 524 | + |
| 525 | + def issues_presentation(self): |
| 526 | + # create instance of dmt reporter |
| 527 | + result = resultParser( |
| 528 | + self.xlp_tbl_x, |
| 529 | + self.mu_patterns, |
| 530 | + self.fuzz_result, |
| 531 | + **self.vmnf_handler |
| 532 | + ) |
| 533 | + # call reporter |
| 534 | + result.show_issues() |
| 535 | + |
| 536 | + def start(self): |
| 537 | + |
| 538 | + _scope_ = {} |
| 539 | + target_list = [] |
| 540 | + port_list = [] |
| 541 | + invalid_targets = [] |
| 542 | + port_step = '' |
| 543 | + |
| 544 | + dmt_handler= argparse.Namespace( |
| 545 | + ignore_state = False, # ignore state - disable IP and port state verification |
| 546 | + single_target = False, # single target scope |
| 547 | + scope = False, # file with a list of targets |
| 548 | + range = False, # ip range, 192.168.12.0-20 |
| 549 | + cidr = False, # cidr range: 192.168.32.0/26 |
| 550 | + port = False, # single port verification |
| 551 | + single_port = None, # single port verification |
| 552 | + portr = False, # port range: 8000-8010 |
| 553 | + portl = False, # port list: 8999, 5001, 9000, 7120 |
| 554 | + debug = False # debug mode |
| 555 | + ) |
| 556 | + |
| 557 | + options = self.parse_args() |
| 558 | + dmt_handler.args = options.parse_known_args( |
| 559 | + namespace=dmt_handler)[1] |
| 560 | + |
| 561 | + if not self.vmnf_handler['scope']: |
| 562 | + print(VimanaSharedArgs().shared_help.__doc__) |
| 563 | + sys.exit(1) |
| 564 | + |
| 565 | + # here we just need to get a list of valid scope |
| 566 | + targets_ports_set = get_tool_scope(**self.vmnf_handler) |
| 567 | + self.tps = targets_ports_set |
| 568 | + |
| 569 | + ports = [] |
| 570 | + for p in targets_ports_set: |
| 571 | + ports.append(p.split(':')[1].strip()) |
| 572 | + |
| 573 | + self.last_step = False |
| 574 | + self.debug = dmt_handler.debug |
| 575 | + self.exp_mode = False |
| 576 | + start = True |
| 577 | + last_step = False |
| 578 | + server_flag_found = False |
| 579 | + request_fail = 0 |
| 580 | + |
| 581 | + for entry in targets_ports_set: |
| 582 | + ''' have to change this to auto choose the right scheme''' |
| 583 | + self.target = 'http://' + entry |
| 584 | + port = entry.split(':')[1].strip() |
| 585 | + |
| 586 | + dmt_start = datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
| 587 | + c_target = colored(self.target,'green') |
| 588 | + cprint("[{0}] Starting DMT against {1}...".format(datetime.now(),c_target), 'cyan') |
| 589 | + sleep(1) |
| 590 | + |
| 591 | + xvals = ['_','.','','^','~','-'] |
| 592 | + fakefile = "/{}{}".format( |
| 593 | + random.choice(xvals), |
| 594 | + self.random_value(random.choice(range(1,6))) |
483 | 595 | ) |
484 | | - sys.stdout.flush() |
485 | | - sleep(self.vmnf_handler['wait']) |
486 | | - up_c += 1 |
487 | | - |
488 | | - # up request |
489 | | - self.vmnf_handler['target_url'] = expanded_pattern |
| 596 | + base_r = self.target |
| 597 | + payload_ = base_r + fakefile |
| 598 | + |
| 599 | + self.vmnf_handler['target_url'] = payload_ |
490 | 600 | response = createSession(**self.vmnf_handler) |
491 | | - self.expanded_response = self.get_unescape_html(response.text) |
| 601 | + |
| 602 | + if response is None: |
| 603 | + # because with target --port will be just one port, doesnt need such control like request_fail |
| 604 | + |
| 605 | + if not self.vmnf_handler['single_port']: |
| 606 | + # control request fails to improve consistence of module |
| 607 | + request_fail += 1 |
| 608 | + if request_fail > 3: |
| 609 | + request_fail = 0 |
| 610 | + print("\nHi, sadhu! Too many fails in this process, try to discovery host before!") |
| 611 | + |
| 612 | + cprint('''[{}] DMT did not receive a valid response from the target, nothing to do. |
| 613 | + '''.format(datetime.now()), 'red', attrs=[]) |
| 614 | + |
| 615 | + # to continue testing other ports |
| 616 | + if (len(targets_ports_set) > 1): |
| 617 | + continue |
| 618 | + else: |
| 619 | + break |
| 620 | + |
| 621 | + current_response = self.get_unescape_html(response.text) |
492 | 622 | response_status = response.status_code |
| 623 | + found_exception_flag = True if 'Exception Type' \ |
| 624 | + in current_response else False |
| 625 | + |
| 626 | + if start or not server_flag_found: |
| 627 | + '''just to check if there is any known django/python keyword in response headers''' |
493 | 628 |
|
494 | | - if self.expanded_response: |
495 | | - if response_status == 404: |
| 629 | + start = False |
| 630 | + # just a test to blackbox fingerprint... |
| 631 | + flags = [ |
| 632 | + 'Python','WSGIServer', 'CPython', |
| 633 | + 'Django', 'CherryPy', 'gunicorn', |
| 634 | + 'Flask','web2py', 'mod_wsgi', 'APACHE' |
| 635 | + ] |
| 636 | + |
| 637 | + for header in response.headers: |
| 638 | + for flag in flags: |
| 639 | + flag = flag.lower() |
| 640 | + try: |
| 641 | + value = (response.headers[header]) |
| 642 | + except KeyError: |
| 643 | + continue |
| 644 | + |
| 645 | + if flag in header.lower() or flag in value.lower(): |
| 646 | + server_flag_found = True |
| 647 | + header = str(' → ' + header + ": ") |
| 648 | + print("\n") |
| 649 | + self.print_it(header, value) |
| 650 | + |
| 651 | + self.expanded_response = current_response |
| 652 | + self.dmt_start_request = current_response |
| 653 | + self.dmt_start_base_r = base_r |
| 654 | + self.dmt_start_port = port |
| 655 | + self.dmt_start_last_step= last_step |
| 656 | + |
| 657 | + if response_status == 400: |
| 658 | + if found_exception_flag: |
| 659 | + self.handle_discovery_xt() |
| 660 | + else: |
| 661 | + print('''\n[dmt: {}]: The target does not appear to be vulnerable. |
| 662 | + \rMake sure that the analysis settings are correct:\n'''.format( |
| 663 | + datetime.now() |
| 664 | + ) |
| 665 | + ) |
| 666 | + for set_k, set_v in (self.vmnf_handler.items()): |
| 667 | + if set_k != 'scope' and set_v: |
| 668 | + print('{}{}:\t{}'.format( |
| 669 | + (' ' * int(5-len(set_k) + 10)),set_k, |
| 670 | + colored(set_v, 'blue') |
| 671 | + ) |
| 672 | + ) |
| 673 | + sys.exit(1) |
| 674 | + |
| 675 | + if response_status == 404: |
| 676 | + # Check if last step |
| 677 | + if (targets_ports_set.index(entry) + 1) == (len(targets_ports_set)): |
| 678 | + last_step = True |
| 679 | + |
| 680 | + if self.debug_is_true(): |
| 681 | + '''status is 404 and DEBUG is True so run another tests''' |
| 682 | + |
| 683 | + # Basic DMT actions |
496 | 684 | self.get_url_patterns() |
| 685 | + self.expand_UP() |
| 686 | + self.check_api_auth_points() |
| 687 | + self.check_django_adm() |
| 688 | + |
| 689 | + # extending DMT: Call DJunch fuzzer and create instances of object result |
| 690 | + # this result, a list of dictionaries (2) will be used to resultParser |
| 691 | + self.fuzz_result = Djunch( |
| 692 | + base_r, self.expanded_patterns, |
| 693 | + **self.vmnf_handler).start() |
497 | 694 |
|
498 | | - if self.vmnf_handler['debug']: |
499 | | - self.djmimic() |
500 | | - elif response_status == 500: |
501 | | - self.dxt_parser(self.expanded_response, False, True) |
| 695 | + response = (future.result()) |
| 696 | + self.expanded_response = self.get_unescape_html(response.text) |
| 697 | + response_status = response.status_code |
| 698 | + |
| 699 | + # print("\nThreaded time:", time.time() - threaded_start) |
| 700 | + if self.expanded_response: |
| 701 | + if response_status == 404: |
| 702 | + self.get_url_patterns() |
| 703 | + if self.vmnf_handler['debug']: |
| 704 | + self.djmimic() |
| 705 | + elif response_status == 500: |
| 706 | + self.dxt_parser(self.expanded_response, False, True) |
502 | 707 |
|
503 | | - print() |
504 | | - print(self.xlp_tbl_x) |
| 708 | + print() |
| 709 | + print(self.xlp_tbl_x) |
505 | 710 |
|
506 | 711 | return self.expanded_patterns |
507 | 712 |
|
|
0 commit comments