|
@@ -0,0 +1,237 @@
|
|
|
+# Created by Aidmar
|
|
|
+
|
|
|
+import logging
|
|
|
+import math
|
|
|
+from operator import itemgetter
|
|
|
+import operator
|
|
|
+from random import randint, uniform
|
|
|
+
|
|
|
+from lea import Lea
|
|
|
+
|
|
|
+from Attack import BaseAttack
|
|
|
+from Attack.AttackParameters import Parameter as Param
|
|
|
+from Attack.AttackParameters import ParameterTypes
|
|
|
+
|
|
|
+logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
|
|
|
+# noinspection PyPep8
|
|
|
+from scapy.utils import RawPcapReader
|
|
|
+from scapy.layers.inet import IP, Ether, TCP, RandShort
|
|
|
+#from scapy.all import *
|
|
|
+
|
|
|
+
|
|
|
+class JoomlaRegPrivExploit(BaseAttack.BaseAttack):
|
|
|
+ # Metasploit default packet rate
|
|
|
+ maxDefaultPPS = 55
|
|
|
+ minDefaultPPS = 5
|
|
|
+ # HTTP port
|
|
|
+ http_port = 80
|
|
|
+ # Metasploit experiments show this range of ports
|
|
|
+ minDefaultPort = 30000
|
|
|
+ maxDefaultPort = 50000
|
|
|
+
|
|
|
+ def __init__(self, statistics, pcap_file_path):
|
|
|
+ """
|
|
|
+ Creates a new instance of the Joomla Registeration Privileges Escalation Exploit.
|
|
|
+
|
|
|
+ :param statistics: A reference to the statistics class.
|
|
|
+ """
|
|
|
+ # Initialize attack
|
|
|
+ super(JoomlaRegPrivExploit, self).__init__(statistics, "JoomlaRegPrivesc Exploit", "Injects an JoomlaRegPrivesc exploit'",
|
|
|
+ "Resource Exhaustion")
|
|
|
+
|
|
|
+ # Define allowed parameters and their type
|
|
|
+ self.supported_params = {
|
|
|
+ Param.MAC_SOURCE: ParameterTypes.TYPE_MAC_ADDRESS,
|
|
|
+ Param.IP_SOURCE: ParameterTypes.TYPE_IP_ADDRESS,
|
|
|
+ Param.MAC_DESTINATION: ParameterTypes.TYPE_MAC_ADDRESS,
|
|
|
+ Param.IP_DESTINATION: ParameterTypes.TYPE_IP_ADDRESS,
|
|
|
+ Param.TARGET_HOST: ParameterTypes.TYPE_URI,
|
|
|
+ Param.TARGET_URI: ParameterTypes.TYPE_URI,
|
|
|
+ Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
|
|
|
+ Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
|
|
|
+ Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT
|
|
|
+ }
|
|
|
+
|
|
|
+ # PARAMETERS: initialize with default utilsvalues
|
|
|
+ # (values are overwritten if user specifies them)
|
|
|
+ most_used_ip_address = self.statistics.get_most_used_ip_address()
|
|
|
+ if isinstance(most_used_ip_address, list):
|
|
|
+ most_used_ip_address = most_used_ip_address[0]
|
|
|
+ self.add_param_value(Param.IP_SOURCE, most_used_ip_address)
|
|
|
+ self.add_param_value(Param.MAC_SOURCE, self.statistics.get_mac_address(most_used_ip_address))
|
|
|
+ self.add_param_value(Param.TARGET_URI, "/")
|
|
|
+ self.add_param_value(Param.TARGET_HOST, "www.hackme.com")
|
|
|
+ self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
|
|
|
+ self.add_param_value(Param.PACKETS_PER_SECOND,self.maxDefaultPPS)
|
|
|
+
|
|
|
+ # victim configuration
|
|
|
+ # consider that the destination has port 80 opened
|
|
|
+ random_ip_address = self.statistics.get_random_ip_address()
|
|
|
+ self.add_param_value(Param.IP_DESTINATION, random_ip_address)
|
|
|
+
|
|
|
+ destination_mac = self.statistics.get_mac_address(random_ip_address)
|
|
|
+ if isinstance(destination_mac, list) and len(destination_mac) == 0:
|
|
|
+ destination_mac = self.generate_random_mac_address()
|
|
|
+ self.add_param_value(Param.MAC_DESTINATION, destination_mac)
|
|
|
+
|
|
|
+ @property
|
|
|
+ def generate_attack_pcap(self):
|
|
|
+ def update_timestamp(timestamp, pps, maxdelay):
|
|
|
+ """
|
|
|
+ Calculates the next timestamp to be used based on the packet per second rate (pps) and the maximum delay.
|
|
|
+
|
|
|
+ :return: Timestamp to be used for the next packet.
|
|
|
+ """
|
|
|
+ return timestamp + uniform(1 / pps, maxdelay)
|
|
|
+
|
|
|
+
|
|
|
+ # Timestamp
|
|
|
+ timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
|
|
|
+ # TO-DO: find better pkt rate
|
|
|
+ pps = self.get_param_value(Param.PACKETS_PER_SECOND)
|
|
|
+ randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 30, 5 / pps: 15, 10 / pps: 3})
|
|
|
+
|
|
|
+ # Aidmar
|
|
|
+ # Calculate the complement packet rates of the background traffic packet rates per interval
|
|
|
+ result = self.statistics.process_db_query(
|
|
|
+ "SELECT timestamp,pktsCount FROM interval_statistics ORDER BY timestamp")
|
|
|
+ print(result)
|
|
|
+ bg_interval_pps = []
|
|
|
+ intervalsSum = 0
|
|
|
+ if result:
|
|
|
+ # Get the interval in seconds
|
|
|
+ for i, row in enumerate(result):
|
|
|
+ if i < len(result) - 1:
|
|
|
+ intervalsSum += math.ceil((int(result[i + 1][0]) * 10 ** -6) - (int(row[0]) * 10 ** -6))
|
|
|
+ interval = intervalsSum / (len(result) - 1)
|
|
|
+ # Convert timestamp from micro to seconds, convert packet rate "per interval" to "per second"
|
|
|
+ for row in result:
|
|
|
+ bg_interval_pps.append((int(row[0]) * 10 ** -6, int(row[1] / interval)))
|
|
|
+ # Find max PPS
|
|
|
+ maxPPS = max(bg_interval_pps, key=itemgetter(1))[1]
|
|
|
+ complement_interval_pps = []
|
|
|
+ for row in bg_interval_pps:
|
|
|
+ complement_interval_pps.append((row[0], int(pps * (maxPPS - row[1]) / maxPPS)))
|
|
|
+ print(complement_interval_pps)
|
|
|
+
|
|
|
+ def getIntervalPPS(timestamp):
|
|
|
+ for row in complement_interval_pps:
|
|
|
+ if timestamp <= row[0]:
|
|
|
+ return row[1]
|
|
|
+ return complement_interval_pps[-1][1] # in case the timstamp > capture max timestamp
|
|
|
+
|
|
|
+ # Initialize parameters
|
|
|
+ packets = []
|
|
|
+ mac_source = self.get_param_value(Param.MAC_SOURCE)
|
|
|
+ ip_source = self.get_param_value(Param.IP_SOURCE)
|
|
|
+ mac_destination = self.get_param_value(Param.MAC_DESTINATION)
|
|
|
+ ip_destination = self.get_param_value(Param.IP_DESTINATION)
|
|
|
+ target_host = self.get_param_value(Param.TARGET_HOST)
|
|
|
+ target_uri = self.get_param_value(Param.TARGET_URI)
|
|
|
+
|
|
|
+ # Aidmar - check ip.src == ip.dst
|
|
|
+ if ip_source == ip_destination:
|
|
|
+ print("\nERROR: Invalid IP addresses; source IP is the same as destination IP: " + ip_source + ".")
|
|
|
+ import sys
|
|
|
+ sys.exit(0)
|
|
|
+
|
|
|
+ path_attack_pcap = None
|
|
|
+ replayDelay = self.get_reply_delay(ip_destination)
|
|
|
+
|
|
|
+ # Inject Joomla_registration_privesc
|
|
|
+ # Read joomla_registration_privesc pcap file
|
|
|
+ orig_ip_dst = None
|
|
|
+ exploit_raw_packets = RawPcapReader("joomla_registration_privesc.pcap")
|
|
|
+
|
|
|
+ port_source = randint(self.minDefaultPort,self.maxDefaultPort) # experiments show this range of ports
|
|
|
+
|
|
|
+ for pkt_num, pkt in enumerate(exploit_raw_packets):
|
|
|
+ eth_frame = Ether(pkt[0])
|
|
|
+ ip_pkt = eth_frame.payload
|
|
|
+ tcp_pkt = ip_pkt.payload
|
|
|
+ str_http_pkt = str(tcp_pkt.payload)
|
|
|
+
|
|
|
+ if pkt_num == 0:
|
|
|
+ prev_orig_port_source = tcp_pkt.getfieldval("sport")
|
|
|
+ if tcp_pkt.getfieldval("dport") == self.http_port:
|
|
|
+ orig_ip_dst = ip_pkt.getfieldval("dst") # victim IP
|
|
|
+
|
|
|
+ # Request
|
|
|
+ if ip_pkt.getfieldval("dst") == orig_ip_dst: # victim IP
|
|
|
+
|
|
|
+ # There are 7 TCP connections with different source ports, for each of them we generate random port
|
|
|
+ if tcp_pkt.getfieldval("sport") != prev_orig_port_source:
|
|
|
+ port_source = randint(self.minDefaultPort, self.maxDefaultPort)
|
|
|
+ prev_orig_port_source = tcp_pkt.getfieldval("sport")
|
|
|
+
|
|
|
+ # Ether
|
|
|
+ eth_frame.setfieldval("src", mac_source)
|
|
|
+ eth_frame.setfieldval("dst", mac_destination)
|
|
|
+ # IP
|
|
|
+ ip_pkt.setfieldval("src", ip_source)
|
|
|
+ ip_pkt.setfieldval("dst", ip_destination)
|
|
|
+ # TCP
|
|
|
+ tcp_pkt.setfieldval("sport",port_source)
|
|
|
+
|
|
|
+
|
|
|
+ eth_frame.payload = b''
|
|
|
+ ip_pkt.payload = b''
|
|
|
+ tcp_pkt.payload = b''
|
|
|
+ #temp = "GET / HTTP/1.0\r\n\r\n"
|
|
|
+ #temp = "GET / HTTP/1.1\r\nHost: 192.168.189.1\r\nUser-Agent: Mozilla/4.0(compatible;MSIE6.0;WindowsNT5.1)\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\n"
|
|
|
+
|
|
|
+ if len(str_http_pkt) > 0:
|
|
|
+ # convert payload bytes to str => str = "b'..\\r\\n..'"
|
|
|
+ str_http_pkt = str_http_pkt[2:-1]
|
|
|
+ str_http_pkt = str_http_pkt.replace('/joomla360', target_uri)
|
|
|
+ str_http_pkt = str_http_pkt.replace(orig_ip_dst, target_host)
|
|
|
+ str_http_pkt = str_http_pkt.replace("\\n", "\n")
|
|
|
+ str_http_pkt = str_http_pkt.replace("\\r", "\r")
|
|
|
+
|
|
|
+ new_pkt = (eth_frame / ip_pkt/ tcp_pkt / str_http_pkt)
|
|
|
+ new_pkt.time = timestamp_next_pkt
|
|
|
+
|
|
|
+ maxdelay = randomdelay.random()
|
|
|
+ pps = self.minDefaultPPS if getIntervalPPS(timestamp_next_pkt) is None else max(
|
|
|
+ getIntervalPPS(timestamp_next_pkt), self.minDefaultPPS)
|
|
|
+ timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
|
|
|
+ # Reply
|
|
|
+ else:
|
|
|
+ # Ether
|
|
|
+ eth_frame.setfieldval("src", mac_destination)
|
|
|
+ eth_frame.setfieldval("dst", mac_source)
|
|
|
+ # IP
|
|
|
+ ip_pkt.setfieldval("src", ip_destination)
|
|
|
+ ip_pkt.setfieldval("dst", ip_source)
|
|
|
+ # TCP
|
|
|
+ tcp_pkt.setfieldval("dport", port_source)
|
|
|
+
|
|
|
+ eth_frame.payload = b''
|
|
|
+ ip_pkt.payload = b''
|
|
|
+ tcp_pkt.payload = b''
|
|
|
+
|
|
|
+ if len(str_http_pkt) > 0:
|
|
|
+ # convert payload bytes to str => str = "b'..\\r\\n..'"
|
|
|
+ str_http_pkt = str_http_pkt[2:-1]
|
|
|
+ str_http_pkt = str_http_pkt.replace('/joomla360', target_uri)
|
|
|
+ str_http_pkt = str_http_pkt.replace(orig_ip_dst, target_host)
|
|
|
+ str_http_pkt = str_http_pkt.replace("\\n", "\n")
|
|
|
+ str_http_pkt = str_http_pkt.replace("\\r", "\r")
|
|
|
+
|
|
|
+ new_pkt = (eth_frame / ip_pkt / tcp_pkt / str_http_pkt)
|
|
|
+ timestamp_next_pkt = timestamp_next_pkt + uniform(replayDelay, 2 * replayDelay)
|
|
|
+ new_pkt.time = timestamp_next_pkt
|
|
|
+
|
|
|
+ packets.append(new_pkt)
|
|
|
+
|
|
|
+ # Store timestamp of first packet (for attack label)
|
|
|
+ self.attack_start_utime = packets[0].time
|
|
|
+ self.attack_end_utime = packets[-1].time
|
|
|
+
|
|
|
+ if len(packets) > 0:
|
|
|
+ packets = sorted(packets, key=lambda pkt: pkt.time)
|
|
|
+ path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
|
|
|
+
|
|
|
+ # return packets sorted by packet time_sec_start
|
|
|
+ # pkt_num+1: because pkt_num starts at 0
|
|
|
+ return pkt_num + 1, path_attack_pcap
|