Distributed Content Discovery With Aws and Java
Github: https://github.com/thoughtfault/cloudbuster
Summary
I wanted to brush up on my java so I created a web content discovery tool that uses ec2 instances to make HTTP requests on the users behalf.
This is completely outclassed by tools such as https://github.com/pry0cc/axiom, https://github.com/fyoorer/ShadowClone and https://github.com/DotNetRussell/Ensemble and makes no sense to use in the real world.
Cool example
Launch a content discovery job with 12 workers from Tokyo, London, and Sydney for total 120gbps bandwidth.
cloudbuster --url https://TARGET/ --wordlist bigwordlist.txt --regions us-east-1,us-east-2,us-west-1 --count 4 --type m4.10xlarge
Development
We have two programs - the first is run from the attackers machine and deploys and configures the ec2 workers, as well as distributing the wordlist and aggregating results to the attackers console. The second is run on the ec2 workers and is a generic content discovery tool similar to dirbuster/gobuster/feroxbuster. It will read the section of the wordlist distributed to it and write the results to a file which our first program will collect and display to the attacker.
.
├── Main.java
├── Infrastructure.java
├── Manager.java
└── RegionalInfrastructure.java
├── Helpers.java
Contents of Main.java
package org.example;
import com.jcraft.jsch.*;
import org.apache.commons.cli.*;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import static org.example.Helpers.print;
import static org.example.Helpers.readFile;
/**
* This class parses command line inputs and polls workers for output
*/
public class Main {
// Argument defaults
private static String target;
private static String wordlistPath;
public static String[] extensions;
private static String[] regions = {"us-west-2", "us-east-2"};
public static int instanceCount = 4;
public static String instanceType = "t2.micro";
private static int mode = 0;
private static ArrayList<String> filterStatusCodes = new ArrayList<>();
private static ArrayList<String> filterContentSizes = new ArrayList<>();
// Global
private static Manager manager;
private static String keyName;
public static int maxRetries = 10;
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
if (mode == 0) {
for (String region : regions) {
new Infrastructure(region).stop();
}
}
}
});
Options options = new Options();
options.addOption("h", "help", false, "show this help message");
options.addOption("d", "destroy", false, "Destroy infrastructure in specified regions");
options.addOption("u", "url", true, "The target url");
options.addOption("w", "wordlist", true,"The wordlist to use");
options.addOption("x", "extensions", true, "Comma seperated extensions to use");
options.addOption("r", "regions", true, "N. Virginia - us-east-\n Ohio - us-east-2\n N. California - us-west-1\n Oregon - us-west-2\n GovCloud West - us-gov-west-1\n GovCloud East - us-gov-east-1\n Canada - ca-central-1\n Stockholm - eu-north-1\n Ireland - eu-west-1\n London - eu-west-2\n Paris - eu-west-3\n Frankfurt - eu-central-1\n Milan - eu-south-1\n Cape Town - af-south-1\n Tokyo - ap-northeast-1\n Seoul - ap-northeast-2\n Osaka - ap-northeast-3\n Singapore - ap-southeast-1\n Sydney - ap-southeast-2\n Jakarta - ap-southeast-3\n Hong Kong - ap-east-1\n Mumbai - ap-south-1\n Sao Paulo - sa-east-1\n Bahrain - me-south-1\n Beijing - cn-north-1\n Ningxia - cn-northwest-1\n");
options.addOption("c", "count", true, "The number of instances to use");
options.addOption("t", "type", true, "The instance type");
options.addOption("s", "status", true, "Status codes to filter out");
options.addOption("c", "content", true, "Content lengths to filter out");
options.addOption("v", "verbose", false, "To enable verbose output");
CommandLineParser parser = new DefaultParser();
try {
CommandLine cmd = parser.parse(options, args);
if (cmd.hasOption("h")) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("cloudbuster", options);
return;
}
if (cmd.hasOption("d")) {
mode = 1;
}
if (cmd.hasOption("u")) {
target = cmd.getOptionValue("u");
} else if (!cmd.hasOption("d")) {
print("A target is required (-u/--url)", 1);
return;
}
if (cmd.hasOption("w")) {
wordlistPath = cmd.getOptionValue("w");
} else if (!cmd.hasOption("d")) {
print("A wordlist is required (-w/--wordlist)", 1);
return;
}
if (cmd.hasOption("x")) {
extensions = cmd.getOptionValue("x").split(",");
}
if (cmd.hasOption("r")) {
regions = cmd.getOptionValue("r").split(",");
}
if (cmd.hasOption("c")) {
instanceCount = Integer.parseInt(cmd.getOptionValue("c"));
}
if (cmd.hasOption("t")) {
instanceType = cmd.getOptionValue("2");
}
if (cmd.hasOption("s")) {
for (String statusCode : cmd.getOptionValue("s").split(",")) {
filterStatusCodes.add(statusCode);
}
} else {
filterStatusCodes.add("404");
}
if (cmd.hasOption("c")) {
for (String contentSize : cmd.getOptionValue("c").split(",")) {
filterContentSizes.add(contentSize);
}
}
if (cmd.hasOption("v")) {
Helpers.verbose = true;
}
} catch (ParseException e) {
print("Invalid command line arguments: " + e.getMessage(), 1);
return;
}
ArrayList<Thread> regionalWorkers = new ArrayList<>();
for (int i = 0; i < regions.length; i++) {
regionalWorkers.add(new Thread(new RegionalInfrastructure(regions[i], mode)));
regionalWorkers.get(i).start();
}
for (Thread worker: regionalWorkers) {
try {
worker.join();
} catch (InterruptedException ignore) {}
}
}
public static void startDiscovery(Infrastructure infrastructure) {
ArrayList<String> addresses = infrastructure.getAddresses();
manager = new Manager(wordlistPath);
keyName = infrastructure.keyName;
print("Starting pollers", 0);
ArrayList<Thread> pollers = new ArrayList<>();
for (int i = 0; i < addresses.size(); i++) {
pollers.add(new Thread(new Poller(addresses.get(i))));
pollers.get(i).start();
}
for (Thread poller : pollers) {
try {
poller.join();
} catch (InterruptedException ignore) {}
}
}
private static class Poller implements Runnable {
private String address;
public Poller(String address) {
this.address = address;
}
@Override
public void run() {
print("Establishing ssh session to " + address, 0);
Session session;
int retryCounter = 0;
while (true) {
try {
JSch jsch = new JSch();
session = jsch.getSession("root", address, 22);
jsch.setConfig("StrictHostKeyChecking", "no");
jsch.addIdentity(keyName + ".pem");
session.connect();
break;
} catch (JSchException exp) {
print("An error occured while establishing the ssh session to " + address + ": " + exp.toString(), 1);
if (retryCounter != maxRetries) {
retryCounter += 1;
try {
Thread.sleep(3000);
} catch (InterruptedException ignore) {}
continue;
}
print("The maximum number of retries has been reached for " + address + ". Exiting now", 1);
System.exit(1);
}
}
Channel commandChannel;
try {
commandChannel = session.openChannel("exec");
} catch (JSchException exp) {
print("An error occured while esablishing a command channel with " + address + ": " + exp.toString(), 1);
return;
}
print("Removing old output files", 0);
((ChannelExec) commandChannel).setCommand("rm /opt/*.txt");
try {
commandChannel.connect();
} catch (JSchException exp) {
print("An error occured while connecting to a command channel for " + address + ": " + exp.toString(), 1);
return;
}
print("Establishing sftp channel to " + address, 0);
ChannelSftp fileChannel;
try {
fileChannel = (ChannelSftp) session.openChannel("sftp");
fileChannel.connect();
} catch (JSchException exp) {
print("An error occured when establishing the sftp channel to " + address + ": " + exp.toString(), 1);
return;
}
int chunkSize;
while (true) {
chunkSize = manager.getChunkSize();
if (chunkSize == 0) {
session.disconnect();
return;
}
String filename = "data/chunk-" + chunkSize + ".txt";
String output = filename.replace("chunk", "output");
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
ArrayList<String> segment = manager.take();
if (segment == null) {
return;
}
for (String line : segment) {
writer.write(target + line + "\n");
}
writer.flush();
} catch (IOException exp) {
print("An error occured while writing the wordlist segment: " + exp.toString(), 1);
return;
}
try {
fileChannel.put(filename, "/opt/wordlist.txt");
} catch (SftpException exp) {
System.err.println("An error occured while transfering the wordlist to " + address + ": " + exp.toString());
return;
}
while (true) {
SftpATTRS attributes;
try {
attributes = fileChannel.stat("/opt/output.txt");
if (attributes != null) {
fileChannel.get("/opt/output.txt", output);
fileChannel.rm("/opt/output.txt");
break;
}
} catch (SftpException sftpExp) {
try {
Thread.sleep(3000);
} catch (InterruptedException intExp) {
System.err.println("An error occured while waiting for output on " + address + ": " + intExp.toString());
return;
}
}
}
for (String line : readFile(output)) {
String[] split_line = line.split(",");
if (!filterStatusCodes.contains(split_line[1]) && !filterContentSizes.contains(split_line[2])) {
System.out.println(split_line[1] + "\t\t" + split_line[2] + "c\t" + address + " -> " + split_line[0]);
}
}
}
}
}
}
Contents of Infrastructure.java
package org.example;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2ClientBuilder;
import com.amazonaws.services.ec2.model.*;
import com.jcraft.jsch.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.example.Helpers.print;
/**
* This class contains all the methods related to building, configuring, and destroying infrastructure
*/
public class Infrastructure {
private String region;
private final String tagKey = "tag:name";
private final String tagValue = "AUTO-GENERATED BY CLOUDBUSTER";
private static AmazonEC2 ec2Client;
private final String cidr = "172.31.0.0/16";
private String vpcId;
private String subnetId;
public String keyName = "CLOUDBUSTER";
private String securityGroupName = "CLOUDBUSTER";
private String userData = "sudo apt update -y; sudo apt update -y; sudo apt install openjdk-17-jdk openjdk-17-jre -y; sudo chown :ubuntu /opt; sudo chmod g+w /opt; echo -e \"[Unit]\\nDescription=Jarbuster\\n\\n[Service]\\nType=simple\\nExecStart=java -jar /home/ubuntu/jarbuster.jar\\nStandardOutput=/var/log/jarbuster.log\\nStandardError=/var/log/jarbuster.log\\n\\n[Install]\\nWantedBy=multi-user.target\" | sudo tee /lib/systemd/system/jarbuster.service; sudo systemctl daemon-reload; sudo sed -i 's/#PermitRootLogin.*/PermitRootLogin without-password/g' /etc/ssh/sshd_config; cat /home/ubuntu/.ssh/authorized_keys | sudo tee /root/.ssh/authorized_keys; sudo rm -f /opt/*.txt; sudo systemctl enable --now jarbuster";
private ArrayList<String> addresses;
public Infrastructure(String region) {
this.region = region;
this.ec2Client = AmazonEC2ClientBuilder.standard().withRegion(region).build();
}
/**
* This method gets the public ip addresses of running worker nodes
* @return
*/
public ArrayList<String> getAddresses() {
if (addresses != null) {
return addresses;
}
DescribeInstancesResult describeInstancesResult = ec2Client.describeInstances(
new DescribeInstancesRequest()
.withInstanceIds(describeInstances("running"))
);
ArrayList<String> runningAddresses = new ArrayList<>();
for (Reservation reservation : describeInstancesResult.getReservations()) {
for (Instance instance : reservation.getInstances()) {
runningAddresses.add(instance.getPublicIpAddress());
}
}
return runningAddresses;
}
/**
* This method creates and configures network and endpoint infrastructure
*/
public void create() {
print("Creating infrastructure in " + region, 0);
createNetwork();
createEndpoints();
configureEndpoints();
print("Finished creating infrastructure for " + region, 0);
}
/**
* This method destroyes network and endpoint infrastructure
*/
public void destroy() {
print("Destroying infrastructure in " + region, 0);
destroyEndpoints();
destroyNetwork();
print("Finished destroying infrastructure in " + region, 0);
}
public void stop() {
stopEndpoints();
}
public void start() {
startEndpoints();
}
/**
* This method configures the vpc, subnet, and routing
*/
private void createNetwork() {
if (!alreadyCreated(ResourceType.Vpc)) {
print("Creating VPC in " + region, 0);
CreateVpcResult vpcResult = ec2Client.createVpc(
new CreateVpcRequest()
.withTagSpecifications(getTags(ResourceType.Vpc))
.withCidrBlock(cidr)
);
vpcId = vpcResult.getVpc().getVpcId();
} else {
print("Loading VPC id for " + region, 0);
vpcId = getResource(ResourceType.Vpc);
}
if (!alreadyCreated(ResourceType.Subnet)) {
print("Creating subnet in " + region, 0);
CreateSubnetResult subnetResult = ec2Client.createSubnet(
new CreateSubnetRequest()
.withTagSpecifications(getTags(ResourceType.Subnet))
.withVpcId(vpcId)
.withCidrBlock(cidr)
);
subnetId = subnetResult.getSubnet().getSubnetId();
} else {
print("Loading subnet id for " + region, 0);
subnetId = getResource(ResourceType.Subnet);
}
String routeTableId;
if (!alreadyCreated(ResourceType.RouteTable)) {
print("Creating route table in " + region, 0);
CreateRouteTableResult routeTableResult = ec2Client.createRouteTable(
new CreateRouteTableRequest()
.withTagSpecifications(getTags(ResourceType.RouteTable))
.withVpcId(vpcId)
);
routeTableId = routeTableResult.getRouteTable().getRouteTableId();
print("Associating route table in " + region, 0);
ec2Client.associateRouteTable(
new AssociateRouteTableRequest()
.withRouteTableId(routeTableId)
.withSubnetId(subnetId)
);
} else {
print("Loading route table id for " + region, 0);
routeTableId = getResource(ResourceType.RouteTable);
}
if (!alreadyCreated(ResourceType.InternetGateway)) {
print("Creating internet gateway in " + region, 0);
CreateInternetGatewayResult internetGatewayResult = ec2Client.createInternetGateway(
new CreateInternetGatewayRequest()
.withTagSpecifications(getTags(ResourceType.InternetGateway))
);
String internetGatewayId = internetGatewayResult.getInternetGateway().getInternetGatewayId();
print("Attaching internet gateway in " + region, 0);
AttachInternetGatewayResult attachResult = ec2Client.attachInternetGateway(
new AttachInternetGatewayRequest()
.withVpcId(vpcId)
.withInternetGatewayId(internetGatewayId)
);
print("Creating routes in " + region, 0);
ec2Client.createRoute(
new CreateRouteRequest()
.withRouteTableId(routeTableId)
.withDestinationCidrBlock("0.0.0.0/0")
.withGatewayId(internetGatewayId)
);
ec2Client.createRoute(
new CreateRouteRequest()
.withRouteTableId(routeTableId)
.withDestinationIpv6CidrBlock("::/0")
.withGatewayId(internetGatewayId)
);
}
}
/**
* This method configures the keypair, the security group and the instance
*/
private void createEndpoints() {
if (!alreadyCreated(ResourceType.KeyPair)) {
print("Creating keypair in " + region, 0);
CreateKeyPairResult keyPairResult = ec2Client.createKeyPair(
new CreateKeyPairRequest()
.withTagSpecifications(getTags(ResourceType.KeyPair))
.withKeyName(keyName)
);
print("Saving keypair for " + region, 0);
try (BufferedWriter writer = new BufferedWriter(
new FileWriter(keyName + ".pem"))) {
writer.write(keyPairResult.getKeyPair().getKeyMaterial());
} catch (IOException ignore) {}
} else {
print("Loading keypair for " + region, 0);
}
String groupId;
if (!alreadyCreated(ResourceType.SecurityGroup)) {
print("Creating security group in " + region, 0);
CreateSecurityGroupRequest createSecurityGroupRequest = new CreateSecurityGroupRequest()
.withTagSpecifications(getTags(ResourceType.SecurityGroup))
.withVpcId(vpcId)
.withGroupName(securityGroupName)
.withDescription(securityGroupName);
CreateSecurityGroupResult securityGroupResult = ec2Client.createSecurityGroup(createSecurityGroupRequest);
print("Adding security group ingress rules in " + region, 0);
IpPermission allowSsh = new IpPermission()
.withIpProtocol("tcp")
.withFromPort(22)
.withToPort(22)
.withIpRanges(getMyIp());
ec2Client.authorizeSecurityGroupIngress(
new AuthorizeSecurityGroupIngressRequest()
.withGroupId(securityGroupResult.getGroupId())
.withTagSpecifications(getTags(ResourceType.SecurityGroupRule))
.withIpPermissions(allowSsh)
);
groupId = securityGroupResult.getGroupId();
} else {
print("Loading security group for " + region, 0);
groupId = getResource(ResourceType.SecurityGroup);
}
if (describeInstances("running").isEmpty() && describeInstances("stopped").isEmpty()) {
print("Launching ec2 instance(s) in " + region, 0);
String imageId = ec2Client.describeImages(
new DescribeImagesRequest()
.withOwners("099720109477")
.withFilters(
new Filter("name").withValues("ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"),
new Filter("state").withValues("available"),
new Filter("root-device-type").withValues("ebs"),
new Filter("virtualization-type").withValues("hvm"),
new Filter("architecture").withValues("x86_64")
)
).getImages().get(0).getImageId();
List<InstanceNetworkInterfaceSpecification> interfaces = new ArrayList<InstanceNetworkInterfaceSpecification>();
interfaces.add(new InstanceNetworkInterfaceSpecification().withAssociatePublicIpAddress(true).withDeviceIndex(0).withSubnetId(subnetId).withGroups(groupId));
RunInstancesRequest runInstancesRequest = new RunInstancesRequest()
.withTagSpecifications(getTags(ResourceType.Instance))
.withImageId(imageId)
.withKeyName(keyName)
.withInstanceType(Main.instanceType)
.withMinCount(Main.instanceCount)
.withMaxCount(Main.instanceCount)
.withNetworkInterfaces(interfaces)
.withNetworkInterfaces(interfaces)
.withUserData(userData);
RunInstancesResult runInstancesResult = ec2Client.runInstances(runInstancesRequest);
ArrayList<String> publicIpAddresses = new ArrayList<>();
for (Instance instance : runInstancesResult.getReservation().getInstances()) {
DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest()
.withInstanceIds(instance.getInstanceId());
DescribeInstancesResult describeInstancesResult = null;
while (true) {
describeInstancesResult = ec2Client.describeInstances(describeInstancesRequest);
List<Instance> instanceList = describeInstancesResult.getReservations().get(0).getInstances();
String address = instanceList.get(0).getPublicIpAddress();
if (address != null) {
publicIpAddresses.add(address);
break;
} else {
try {
Thread.sleep(2000);
} catch (InterruptedException ignore) {
}
}
}
}
addresses = publicIpAddresses;
} else {
print("Loading ec2 instance(s) for " + region, 0);
addresses = new ArrayList<>();
List<Reservation> reservations = ec2Client.describeInstances(
new DescribeInstancesRequest()
.withInstanceIds(describeInstances("running"))
).getReservations();
for (Reservation reservation : reservations) {
for (Instance instance : reservation.getInstances()) {
addresses.add(instance.getPublicIpAddress());
}
}
}
}
/**
* This method manages the endpoint configuration
*/
private void configureEndpoints() {
if (addresses.size() == 0) {
return;
}
ArrayList<Thread> installers = new ArrayList<>();
for(int i = 0; i < Main.instanceCount; i++) {
installers.add(new Thread(new Installer(addresses.get(i))));
installers.get(i).start();
}
for (Thread installer : installers) {
try {
installer.join();
} catch (InterruptedException ignore) {}
}
}
/**
* This class configures the endpoints
*/
private class Installer implements Runnable {
private String address;
public Installer(String address) {
this.address = address;
}
@Override
public void run() {
JSch jsch = new JSch();
Session session;
int retryCounter = 0;
while (true) {
try {
print("Creating session to " + address, 0);
session = jsch.getSession("ubuntu", address, 22);
jsch.setConfig("StrictHostKeyChecking", "no");
jsch.addIdentity(keyName + ".pem");
session.connect();
break;
} catch (JSchException sessExp) {
print("An error occured during session establishment with " + address + ": " + sessExp.toString(), 1);
try {
Thread.sleep(3000);
} catch (InterruptedException intExp) {
print("There was an error while waiting for session establishment with " + address + ": " + intExp.toString(), 1);
return;
}
retryCounter += 1;
if (retryCounter == Main.maxRetries) {
print("The maximum number of retries has been reached for " + address + ". Exiting now", 1);
System.exit(1);
}
}
}
print("Transfering jar file to " + address, 0);
ChannelSftp fileChannel;
try {
fileChannel = (ChannelSftp) session.openChannel("sftp");
fileChannel.connect();
} catch (JSchException exp) {
print("An error occured while establishing an sftp channel with " + address + ": " + exp.toString(), 1);
return;
}
try {
fileChannel.put("jarbuster.jar", "/home/ubuntu/jarbuster.jar");
} catch (SftpException exp) {
print("An error occured during the sftp transfer to " + address + ": " + exp.toString(), 1);
return;
}
fileChannel.disconnect();
session.disconnect();
print("Finished deploying " + address, 0);
}
}
/**
* This method destroys network resources
*/
private void destroyNetwork() {
print("Destroying network", 0);
String internetGatewayId = getResource(ResourceType.InternetGateway);
if (internetGatewayId != null) {
print("Destroying internet gateway in " + region, 0);
DescribeInternetGatewaysResult describeInternetGatewaysRequest = ec2Client.describeInternetGateways(
new DescribeInternetGatewaysRequest()
.withInternetGatewayIds(internetGatewayId)
);
ec2Client.detachInternetGateway(
new DetachInternetGatewayRequest()
.withInternetGatewayId(internetGatewayId)
.withVpcId(describeInternetGatewaysRequest.getInternetGateways().get(0).getAttachments().get(0).getVpcId())
);
ec2Client.deleteInternetGateway(
new DeleteInternetGatewayRequest()
.withInternetGatewayId(internetGatewayId)
);
}
String routeTableId = getResource(ResourceType.RouteTable);
if (routeTableId != null) {
print("Destroying route table in " + region, 0);
DescribeRouteTablesResult describeRouteTablesResult = ec2Client.describeRouteTables(
new DescribeRouteTablesRequest()
.withRouteTableIds(routeTableId)
);
ec2Client.disassociateRouteTable(
new DisassociateRouteTableRequest()
.withAssociationId(describeRouteTablesResult.getRouteTables().get(0).getAssociations().get(0).getRouteTableAssociationId())
);
ec2Client.deleteRouteTable(
new DeleteRouteTableRequest()
.withRouteTableId(routeTableId)
);
}
String subnetId = getResource(ResourceType.Subnet);
if (subnetId != null) {
print("Destroying subnet in " + region, 0);
ec2Client.deleteSubnet(
new DeleteSubnetRequest()
.withSubnetId(subnetId)
);
}
String vpcId = getResource(ResourceType.Vpc);
if (vpcId != null) {
print("Destroying vpc in " + region, 0);
ec2Client.deleteVpc(
new DeleteVpcRequest()
.withVpcId(vpcId)
);
}
}
/**
* This method destroys endpoint resources
*/
private void destroyEndpoints() {
print("Destroying endpoints", 0);
ArrayList<String> cloudbusterInstances = describeInstances("running");
cloudbusterInstances.addAll(describeInstances("stopped"));
if (!cloudbusterInstances.isEmpty()) {
print("Destroying instances in " + region, 0);
ec2Client.terminateInstances(
new TerminateInstancesRequest()
.withInstanceIds(cloudbusterInstances)
);
ensureInstanceState("terminated", cloudbusterInstances.size() + describeInstances("terminated").size());
}
String securityGroupId = getResource(ResourceType.SecurityGroup);
if (securityGroupId != null) {
print("Destroying security group in " + region, 0);
DescribeSecurityGroupsResult describeSecurityGroupsResult = ec2Client.describeSecurityGroups(
new DescribeSecurityGroupsRequest()
.withGroupIds(securityGroupId)
);
for (IpPermission rule : describeSecurityGroupsResult.getSecurityGroups().get(0).getIpPermissions()) {
RevokeSecurityGroupIngressRequest revokeSecurityGroupIngressRequest = new RevokeSecurityGroupIngressRequest();
revokeSecurityGroupIngressRequest.setGroupId(securityGroupId);
revokeSecurityGroupIngressRequest.setIpPermissions(Collections.singletonList(rule));
ec2Client.revokeSecurityGroupIngress(revokeSecurityGroupIngressRequest);
}
ec2Client.deleteSecurityGroup(
new DeleteSecurityGroupRequest()
.withGroupId(securityGroupId)
);
}
String keyPairId = getResource(ResourceType.KeyPair);
if (keyPairId != null) {
print("Destroying keypair in " + region, 0);
DeleteKeyPairRequest deleteKeyPairRequest = new DeleteKeyPairRequest();
deleteKeyPairRequest.setKeyPairId(keyPairId);
ec2Client.deleteKeyPair(deleteKeyPairRequest);
}
}
/**
* This method stops running instances
*/
private void stopEndpoints() {
print("Searching for running instances in " + region, 0);
ArrayList<String> instances = describeInstances("running");
if (!instances.isEmpty()) {
print("Stopping instances in " + region, 0);
ec2Client.stopInstances(
new StopInstancesRequest()
.withInstanceIds(instances)
);
ensureInstanceState("stopped", instances.size() + describeInstances("stopped").size());
}
}
/**
* This method starts running instances
*/
private void startEndpoints() {
print("Searching for stopped instances in " + region, 0);
ArrayList<String> instances = describeInstances("stopped");
if (!instances.isEmpty()) {
print("Starting instances in " + region, 0);
ec2Client.startInstances(
new StartInstancesRequest()
.withInstanceIds(instances)
);
ensureInstanceState("running", instances.size() + describeInstances("running").size());
}
}
/**
* This method is a basic check to determine if infrastructure for this region has been created
* @return - if infrastucture has been created
*/
public boolean created() {
if (getResource(ResourceType.KeyPair) != null) {
return true;
}
return false;
}
/**
* This method checks if cloudbuster instances are running
* @return - if cloudbuster instances are running
*/
public boolean running() {
if (describeInstances("running").size() != 0) {
return true;
}
return false;
}
/**
* This method gets your ip address
* @return - your ip address
*/
private String getMyIp() {
try {
URL url = new URL("https://checkip.amazonaws.com");
try {
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
return in.readLine() + "/32";
} catch (IOException exp) {
return "0.0.0.0/0";
}
} catch (MalformedURLException exp) {
return "0.0.0.0/0";
}
}
/**
* This method describes cloudbuster instances of a given state
* @param state - the state to look for=
* @return - the instance ids of matching instances
*/
private ArrayList<String> describeInstances(String state) {
List<Reservation> reservations = ec2Client.describeInstances(
new DescribeInstancesRequest()
.withFilters(new Filter("instance-state-name", Arrays.asList(state)))
.withFilters(new Filter("tag-key", Arrays.asList(tagKey)))
.withFilters(new Filter("tag-value", Arrays.asList(tagValue)))
).getReservations();
ArrayList<String> instances = new ArrayList<>();
for (Reservation reservation : reservations) {
for (Instance instance : reservation.getInstances()) {
instances.add(instance.getInstanceId());
}
}
return instances;
}
/**
* This method blocks until a specific number of instances reach a certain state
* @param state - the desired state
* @param count - the number of instances required in this state
*/
private void ensureInstanceState(String state, int count) {
while (true) {
if (describeInstances(state).size() == count) {
break;
} else {
try {
Thread.sleep(2000);
} catch (InterruptedException ignore) {}
}
}
}
/**
* This is a method to get a tag object for a given resource type
* @param resourceType - the resource type that is being tagged
* @return - a tag pair
*/
private TagSpecification getTags(ResourceType resourceType) {
return new TagSpecification()
.withResourceType(resourceType)
.withTags(new Tag(tagKey, tagValue));
}
/**
* This method checks if a needed cloudbuster resource has already been created
* @param resourceType - the resource type to check
* @return - if the resource has been created or not
*/
private boolean alreadyCreated(ResourceType resourceType) {
DescribeTagsResult describeTagsRequest= ec2Client.describeTags(
new DescribeTagsRequest()
.withFilters(new Filter("key", Arrays.asList(tagKey)))
.withFilters(new Filter("value", Arrays.asList(tagValue)))
.withFilters(new Filter("resource-type", Arrays.asList(resourceType.toString())))
);
if (describeTagsRequest.getTags().isEmpty()) {
return false;
}
return true;
}
/**
* This method gets the instance id of a cloudbuster resource
*/
private String getResource(ResourceType resourceType) {
DescribeTagsResult describeTagsRequest= ec2Client.describeTags(
new DescribeTagsRequest()
.withFilters(new Filter("key", Arrays.asList(tagKey)))
.withFilters(new Filter("value", Arrays.asList(tagValue)))
.withFilters(new Filter("resource-type", Arrays.asList(resourceType.toString())))
);
if (describeTagsRequest.getTags().size() == 0) {
return null;
}
return describeTagsRequest.getTags().get(0).getResourceId();
}
}
Contents of Manager.java
package org.example;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import static org.example.Helpers.print;
import static org.example.Helpers.readFile;
/**
* This class provides thread-safe wordlist retrival for workers
*/
public class Manager {
private Queue<ArrayList<String>> chunks;
private String target;
/**
* Loads the specified wordlist and applies extensions. Splits into multiple sub-wordlists.
* @param filePath - the filepath of the wordlist to load
*/
public Manager(String filePath) {
print("Loading " + filePath, 0);
ArrayList<String> wordlist = readFile(filePath);
if (Main.extensions != null) {
for (String word : wordlist) {
for (String extension : Main.extensions) {
wordlist.add(word + extension);
}
}
}
java.util.Collections.shuffle(wordlist);
chunks = new LinkedList<>();
int splitSize = wordlist.size() / 30;
for (int i = 0; i < wordlist.size(); i += splitSize) {
chunks.add(new ArrayList<>(wordlist.subList(i, Math.min(i + splitSize, wordlist.size()))));
}
}
/**
* Retrives a sub-wordlist
* @return a sub-wordlist
*/
public synchronized ArrayList<String> take() {
if (chunks.isEmpty()) {
return null;
}
return chunks.remove();
}
public synchronized int getChunkSize() {
return chunks.size();
}
}
Contents of RegionalInfrastructure.java:
package org.example;
/**
* This class is a handler class for the Infrastructure class
*/
public class RegionalInfrastructure implements Runnable {
private String region;
private int mode;
public RegionalInfrastructure(String region, int mode) {
this.region = region;
this.mode = mode;
}
@Override
public void run() {
Infrastructure infrastructure = new Infrastructure(region);
if (mode == 0) {
if (!infrastructure.created()) {
infrastructure.create();
}
if (!infrastructure.running()) {
infrastructure.start();
}
Main.startDiscovery(infrastructure);
} else {
infrastructure.destroy();
}
}
}
Contents of Helpers.java
package org.example;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Scanner;
/**
* This class provides some helper methods for other classes
*/
public class Helpers {
public static boolean verbose = false;
/**
* This method is used for printing output/error messages
* @param message - the message to print
* @param mode - whether the message should be stdout or stderr
*/
public static void print(String message, int mode) {
if (!verbose) {
return;
}
if (mode == 0) {
System.out.println("[***] [" + Thread.currentThread().getName() + "] " + new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new java.util.Date()) + " " + message + "...");
} else {
System.err.println("[***] [" + Thread.currentThread().getName() + "] " + new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new java.util.Date()) + " " + message + "...");
}
}
/**
* Reads a file into an ArrayList
* @param filePath - the filepath to read
* @return - an ArrayList containing the file lines
*/
public static ArrayList<String> readFile(String filePath) {
ArrayList<String> lines = new ArrayList<>();
File file = new File(filePath);
try {
Scanner scanner = new Scanner(file);
while (scanner.hasNextLine()) {
lines.add(scanner.nextLine());
}
} catch (IOException exp) {
print("An error occured while reading a file: " + exp.toString(), 1);
System.exit(1);
}
return lines;
}
}
Jarbuster
.
├── Main.java
└── Manager.java
├── Helpers.java
Contents of Main.java
package org.example;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.example.Helpers.print;
/**
* This class the entry point loop that manages threads and input/output
*/
public class Main {
private static Manager manager;
private static boolean run;
public static void main(String[] args) {
String wordlistPath = "/opt/wordlist.txt";
String outputPath = "/opt/output.txt";
Helpers.verbose = true;
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception ignore) {}
print("Starting thread pool", 0);
ExecutorService pool = Executors.newCachedThreadPool();
while (true) {
print("Waiting for wordlist to appear", 0);
manager = new Manager(wordlistPath);
print("Sending requests", 0);
run = true;
while (run) {
pool.execute(new Task());
}
print("Requests finsihed, waiting for queue to clear", 0);
try {
Thread.sleep(3000);
} catch (InterruptedException ignore) {}
print("Deleting wordlist " + wordlistPath, 0);
File wordlist = new File(wordlistPath);
wordlist.delete();
print("Writing to output", 0);
try {
File output = new File(outputPath);
FileWriter writer = new FileWriter(output);
for (String line : manager.getResults()) {
writer.write(line + "\n");
}
writer.flush();
} catch (IOException exp) {
print("An error occured when writing to " + outputPath + ": " + exp.toString(), 1);
return;
}
}
}
/**
* This class performs a single http request against a target and saves the response
*/
private static class Task implements Runnable {
public void run() {
URL url = manager.getURL();
if (url == null) {
run = false;
return;
}
try {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.connect();
manager.putResult(url.toString(), connection.getResponseCode(), connection.getContentLength());
} catch (Exception exp) {
print("An error occured while making a request to " + url.toString() + ": " + exp.toString(), 1);
}
}
}
}
Contents of Manager.java
package org.example;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
/**
* This class is a thread-safe manager for workers to get inputs and return outputs to
*/
public class Manager {
private Queue<URL> wordlist;
private ArrayList<String> results;
/**
* The default constructor for Manager will block until wordlist input appears
* @param filePath - the filepath to block on
*/
public Manager(String filePath) {
wordlist = new LinkedList<>();
results = new ArrayList<>();
while (true) {
try {
File f = new File(filePath);
Scanner scanner = new Scanner(f);
while (scanner.hasNextLine()) {
try {
URL url = new URL(scanner.nextLine());
wordlist.add(url);
} catch (MalformedURLException ignore) {}
}
if (!wordlist.isEmpty()) {
break;
}
} catch (FileNotFoundException ignore) {}
try {
Thread.sleep(1000);
} catch (InterruptedException ignore) {}
}
}
/**
* This method returns the output ArrayList
* @return - the output ArrayList
*/
public ArrayList<String> getResults() {
return results;
}
/**
* This method feeds urls to workers
* @return - the least recently added item, or null if empty
*/
public synchronized URL getURL() {
if (wordlist.isEmpty()) {
return null;
}
return wordlist.remove();
}
/**
* Adds a comma seperated string to output ArrayList
* @param url - the requested url
* @param statusCode - the status code of the requested url
* @param contentLength - the response content length
*/
public synchronized void putResult(String url, int statusCode, int contentLength) {
results.add(url + "," + String.valueOf(statusCode) + "," + String.valueOf(contentLength));
}
}
Contents of Helpers.java
package org.example;
import java.text.SimpleDateFormat;
/**
* This class provides helper functions
*/
public class Helpers {
public static boolean verbose;
/**
* Helper function to log messages and errors
* @param message - the message to log
* @param mode - used to determine if the message is an error
*/
public static void print(String message, int mode) {
if (!verbose) {
return;
}
if (mode == 0) {
System.out.println("[***] [" + Thread.currentThread().getName() + "] " + new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new java.util.Date()) + " " + message + "...");
} else {
System.err.println("[***] [" + Thread.currentThread().getName() + "] " + new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new java.util.Date()) + " " + message + "...");
}
}
}