Добавлена поддержка локального RCI через ndmq, обновлена версия до 1.4.0

This commit is contained in:
Petro1990 2026-03-13 21:37:42 +03:00
parent e4762d02cd
commit 68e104d0bf
4 changed files with 248 additions and 1 deletions

82
rci_auth.ps1 Normal file
View File

@ -0,0 +1,82 @@
$USER = "admin"
$PASS = "12985654"
$IP = "192.168.60.1"
Write-Host "=== Тест авторизации Keenetic RCI ($IP) ==="
# 1. Получение challenge и cookies
Write-Host "Step 1: Get challenge and session (cookies)..."
$cookie_file = "cookies.txt"
if (Test-Path $cookie_file) { Remove-Item $cookie_file }
$resp = curl.exe -s -i -c $cookie_file "http://$IP/auth"
$realm = ($resp | Select-String "X-NDM-Realm: (.*)" | ForEach-Object { $_.Matches.Groups[1].Value.Trim() })
$challenge = ($resp | Select-String "X-NDM-Challenge: (.*)" | ForEach-Object { $_.Matches.Groups[1].Value.Trim() })
if (-not $challenge) {
Write-Host "Error: Challenge not found in headers." -ForegroundColor Red
exit 1
}
Write-Host " Realm: $realm"
Write-Host " Challenge: $challenge"
# 2. Вычисление MD5
$md5 = [System.Security.Cryptography.MD5]::Create()
$h1_str = "${USER}:${realm}:${PASS}"
$h1_bytes = [System.Text.Encoding]::UTF8.GetBytes($h1_str)
$h1 = [System.BitConverter]::ToString($md5.ComputeHash($h1_bytes)).Replace("-", "").ToLower()
# 5. Перебор вариантов хеширования
Write-Host "Step 2: Brute-forcing Hash variants..."
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$sha512 = [System.Security.Cryptography.SHA512]::Create()
$variants = @{
"SHA256(challenge + MD5)" = [System.BitConverter]::ToString($sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes("${challenge}${h1}"))).Replace("-", "").ToLower()
"SHA256(MD5 + challenge)" = [System.BitConverter]::ToString($sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes("${h1}${challenge}"))).Replace("-", "").ToLower()
"SHA512(challenge + MD5)" = [System.BitConverter]::ToString($sha512.ComputeHash([System.Text.Encoding]::UTF8.GetBytes("${challenge}${h1}"))).Replace("-", "").ToLower()
"SHA512(MD5 + challenge)" = [System.BitConverter]::ToString($sha512.ComputeHash([System.Text.Encoding]::UTF8.GetBytes("${h1}${challenge}"))).Replace("-", "").ToLower()
}
$rci_success = $false
foreach ($v_name in $variants.Keys) {
$v_hash = $variants[$v_name]
Write-Host "`n--- Testing Variant: $v_name ($v_hash) ---"
$json_payload = @{
login = $USER
password = $v_hash
} | ConvertTo-Json -Compress
$payload_file = "payload.json"
$json_payload | Out-File -FilePath $payload_file -Encoding utf8
Write-Host " Sending JSON POST to /auth..."
# Используем -b и -c для работы с куками, -d @file для JSON
$auth_resp = curl.exe -s -i -b $cookie_file -c $cookie_file -X POST -H "Content-Type: application/json" -d "@$payload_file" "http://$IP/auth"
$status = ($auth_resp | Select-String "HTTP/")
Write-Host " Status: $status"
if ($status -match "200 OK") {
Write-Host " SUCCESS! Auth accepted via JSON POST." -ForegroundColor Green
Write-Host " Testing RCI access..."
$rci = curl.exe -s -b $cookie_file "http://$IP/rci/system/hostname"
Write-Host " RCI Response: $rci"
if ($rci -match "hostname") {
$rci_success = $true
break
}
} else {
Write-Host " Failed with status: $status" -ForegroundColor Yellow
$auth_resp | Select-String "{" | ForEach-Object { Write-Host " Server response: $_" }
}
}
if ($rci_success) {
Write-Host "`nFINAL SUCCESS!" -ForegroundColor Green
} else {
Write-Host "`nFINAL FAILURE." -ForegroundColor Red
exit 1
}

55
rci_auth_test.sh Normal file
View File

@ -0,0 +1,55 @@
#!/bin/sh
# rci_auth_test.sh
# Скрипт для тестирования авторизации Keenetic RCI через X-NDM-Challenge
USER="${1:-admin}"
PASS="${2:-password}"
IP="${3:-192.168.1.1}"
echo "=== Тест авторизации Keenetic RCI ($IP) ==="
# 1. Получаем challenge и realm
echo "Шаг 1: Получение challenge..."
resp_headers=$(curl -s -i "http://$IP/rci/system/hostname" | tr -d '\r')
realm=$(echo "$resp_headers" | grep -i "X-NDM-Realm" | cut -d' ' -f2)
challenge=$(echo "$resp_headers" | grep -i "X-NDM-Challenge" | cut -d' ' -f2)
if [ -z "$challenge" ]; then
echo "Ошибка: Не удалось получить challenge. Проверьте IP или доступность RCI."
exit 1
fi
echo " Realm: $realm"
echo " Challenge: $challenge"
# 2. Считаем MD5(login:realm:password)
# -n в echo важно, чтобы не было лишнего перевода строки
h1=$(echo -n "$USER:$realm:$PASS" | md5sum | cut -d' ' -f1)
echo "Шаг 2: MD5(login:realm:password) = $h1"
# 3. Считаем SHA256(challenge + h1)
# challenge — это бинарная строка? Нет, обычно это hex или string.
# В Keenetic challenge (salt) конкатенируется с MD5 хешем (в виде строки).
hash=$(echo -n "$challenge$h1" | sha256sum | cut -d' ' -f1)
echo "Шаг 3: SHA256(challenge + MD5) = $hash"
# 4. Пробуем авторизоваться
echo "Шаг 4: Попытка запроса с хешем..."
# Мы передаем HASH вместо пароля в Basic Auth
result=$(curl -s -u "$USER:$hash" "http://$IP/rci/system/hostname")
if echo "$result" | grep -q "hostname"; then
echo "БИНГО! Авторизация через Basic Auth (Password=Hash) успешна."
echo "Ответ: $result"
else
echo "Метод 1 (Basic Auth) не удался. Пробуем Метод 2 (X-NDM-Auth-Hash)..."
result=$(curl -s -H "X-NDM-Auth-Hash: $hash" "http://$IP/rci/system/hostname")
if echo "$result" | grep -q "hostname"; then
echo "БИНГО! Авторизация через заголовок X-NDM-Auth-Hash успешна."
echo "Ответ: $result"
else
echo "Упс... Оба метода не удались."
echo "Полный ответ:"
echo "$result"
fi
fi

13
rproxy
View File

@ -3,7 +3,7 @@
# Публикация локальных сервисов через SSH-туннели + nginx на VPS
# http://5.104.75.50:3000/Petro1990/rProxy
VERSION="1.3.9"
VERSION="1.4.0"
CONF_DIR="/opt/etc/rproxy"
CONF_FILE="$CONF_DIR/rproxy.conf"
SERVICES_DIR="$CONF_DIR/services"
@ -193,7 +193,18 @@ get_router_ip() {
echo "192.168.1.1"
}
# Метод 2: Через ndmq (Локальный доступ в Entware)
# Этот метод не требует пароля и работает мгновенно
rci_call() {
local command="$1"
ndmq -p "$command" 2>/dev/null
}
enable_router_basic_auth() {
log_info "Настройка локального доступа через RCI/ndmq..."
local hostname=$(rci_call "show system" | grep "hostname" | awk '{print $NF}')
log_info "Хостнейм роутера: $hostname"
}
# Функция оставлена для обратной совместимости, но более не требуется для v1.3.9
return 0
}

99
verify_hash.py Normal file
View File

@ -0,0 +1,99 @@
import struct
import hashlib
def md4(data):
def f(x, y, z): return (x & y) | (~x & z)
def g(x, y, z): return (x & y) | (x & z) | (y & z)
def h(x, y, z): return x ^ y ^ z
def rot(x, n): return ((x << n) | (x >> (32 - n))) & 0xffffffff
msg = data + b'\x80'
while len(msg) % 64 != 56: msg += b'\x00'
msg += struct.pack('<Q', len(data) * 8)
a, b, c, d = 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476
for i in range(0, len(msg), 64):
x = struct.unpack('<16I', msg[i:i+64])
aa, bb, cc, dd = a, b, c, d
for j in range(16):
a = rot((a + f(b, c, d) + x[j]) & 0xffffffff, [3, 7, 11, 19][j % 4])
a, b, c, d = d, a, b, c
for j in range(16):
a = rot((a + g(b, c, d) + x[[0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15][j]] + 0x5a827999) & 0xffffffff, [3, 5, 9, 13][j % 4])
a, b, c, d = d, a, b, c
for j in range(16):
a = rot((a + h(b, c, d) + x[[0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15][j]] + 0x6ed9eba1) & 0xffffffff, [3, 9, 11, 15][j % 4])
a, b, c, d = d, a, b, c
a, b, c, d = (a + aa) & 0xffffffff, (b + bb) & 0xffffffff, (c + cc) & 0xffffffff, (d + dd) & 0xffffffff
return struct.pack('<4I', a, b, c, d).hex()
def verify():
# Данные из дампа пользователя
USER = "admin"
PASS = "12985654"
realm = "Keenetic Xiaomi R3P"
challenge = "LRIGGJJDBXQEZHCPQXOWCRLGFKWMYGXL"
h1 = "7984d1dba41fe075f1d7d1848432aaa0"
browser_hash = "f3aa27cf6a3c1d562eab5a167987289226c519deab7395d0b382cf7436443253"
nt_hash_browser = "b32107cd9056326b61081f4fb2eec1b3"
nt_hash_calc = md4(PASS.encode('utf-16le'))
print(f"NT Hash Calc: {nt_hash_calc} {'!!! MATCH !!!' if nt_hash_calc == nt_hash_browser else ''}")
session_id = "DFXNAXYBKRVQLFXE"
session_cookie = "WFGGPJJTF"
h1_md5 = h1
h1_sha256 = hashlib.sha256(f"{USER}:{realm}:{PASS}".encode()).hexdigest()
h1_md5_utf16 = hashlib.md5(f"{USER}:{realm}:{PASS}".encode('utf-16le')).hexdigest()
h1_sha256_utf16 = hashlib.sha256(f"{USER}:{realm}:{PASS}".encode('utf-16le')).hexdigest()
hashes = {
"PASS": PASS,
"MD5(PASS)": hashlib.md5(PASS.encode()).hexdigest(),
"MD5(USER:REALM:PASS)": h1,
"SHA256(PASS)": hashlib.sha256(PASS.encode()).hexdigest(),
"SHA256(USER:REALM:PASS)": hashlib.sha256(f"{USER}:{realm}:{PASS}".encode()).hexdigest(),
"MD5_UTF16(PASS)": hashlib.md5(PASS.encode('utf-16le')).hexdigest(),
"MD5_UTF16(USER:REALM:PASS)": hashlib.md5(f"{USER}:{realm}:{PASS}".encode('utf-16le')).hexdigest(),
}
salts = {
"challenge": challenge,
"session_id": session_id,
"cookie": session_cookie,
}
salts = {
"challenge": challenge,
"session_id": session_id,
"cookie_name": session_cookie,
"cookie_val": "KCLGIRELMRJBDBNA",
"phpsessid": "8494617368be96016d63e03b211d550a",
}
h1_sha256_full = hashlib.sha256(f"{USER}:{realm}:{PASS}".encode()).hexdigest()
h1_sha256_no_realm = hashlib.sha256(f"{USER}:{PASS}".encode()).hexdigest()
final_variants = {
"SHA256(challenge + SHA256_full)": hashlib.sha256((challenge + h1_sha256_full).encode()).hexdigest(),
"SHA256(challenge + SHA256_no_realm)": hashlib.sha256((challenge + h1_sha256_no_realm).encode()).hexdigest(),
"SHA256(challenge + MD5_h1)": hashlib.sha256((challenge + h1).encode()).hexdigest(),
}
# И вариант с двоеточием
final_variants["SHA256(challenge + ':' + MD5_h1)"] = hashlib.sha256((challenge + ":" + h1).encode()).hexdigest()
print(f"Target Browser Hash: {browser_hash}")
for name, val in final_variants.items():
if val == browser_hash:
print(f"!!! MATCH !!! {name}: {val}")
else:
print(f"Checked {name}: {val}")
if __name__ == "__main__":
verify()
if __name__ == "__main__":
verify()