Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- Config, docs, scripts, and backup manifests - Submodule refs unchanged (m = modified content in submodules) Made-with: Cursor
140 lines
5.5 KiB
Bash
Executable File
140 lines
5.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Security test: OMNL-2 (office 2) user must not access other offices' data or achieve
|
|
# path traversal / command injection. See docs/04-configuration/mifos-omnl-central-bank/OMNL_OFFICE_2_ACCESS_SECURITY_TEST.md
|
|
# Set STRICT_OFFICE_LIST=1 to fail when GET /offices returns other offices or GET /offices/20 returns 200.
|
|
set -euo pipefail
|
|
REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
if [ -f "${REPO_ROOT}/omnl-fineract/.env" ]; then set +u; source "${REPO_ROOT}/omnl-fineract/.env" 2>/dev/null || true; set -u; fi
|
|
|
|
BASE_URL="${OMNL_FINERACT_BASE_URL:-}"
|
|
TENANT="${OMNL_FINERACT_TENANT:-omnl}"
|
|
# Office-2 user (do NOT use app.omnl admin)
|
|
OFFICE2_USER="${OMNL_OFFICE2_TEST_USER:-shamrayan.admin}"
|
|
OFFICE2_PASS="${OMNL_OFFICE2_TEST_PASSWORD:-${OMNL_SHAMRAYAN_ADMIN_PASSWORD:-}}"
|
|
STRICT="${STRICT_OFFICE_LIST:-0}"
|
|
|
|
FAILED=0
|
|
|
|
if [ -z "$BASE_URL" ] || [ -z "$OFFICE2_PASS" ]; then
|
|
echo "Set OMNL_FINERACT_BASE_URL and either OMNL_OFFICE2_TEST_PASSWORD or OMNL_SHAMRAYAN_ADMIN_PASSWORD (office-2 user only)." >&2
|
|
exit 2
|
|
fi
|
|
|
|
CURL_OFFICE2=(-s -S -w "\n%{http_code}" -H "Fineract-Platform-TenantId: ${TENANT}" -H "Content-Type: application/json" -u "${OFFICE2_USER}:${OFFICE2_PASS}")
|
|
|
|
echo "=== OMNL-2 access security test ==="
|
|
echo "Base URL: $BASE_URL"
|
|
echo "User: $OFFICE2_USER"
|
|
echo ""
|
|
|
|
# --- 1. Data isolation: GET /offices; office 2 must be present ---
|
|
echo "[1] Data isolation: GET /offices (office 2 must be visible)..."
|
|
OFFICES_RESP=$(curl "${CURL_OFFICE2[@]}" "${BASE_URL}/offices" 2>/dev/null)
|
|
OFFICES_BODY=$(echo "$OFFICES_RESP" | sed '$d')
|
|
OFFICES_CODE=$(echo "$OFFICES_RESP" | tail -n1)
|
|
if [ "$OFFICES_CODE" = "401" ]; then
|
|
echo " ERROR: Invalid or missing credentials (HTTP 401). Set office-2 user and password." >&2
|
|
exit 2
|
|
fi
|
|
if [ "$OFFICES_CODE" != "200" ]; then
|
|
echo " FAIL: GET /offices returned HTTP $OFFICES_CODE" >&2
|
|
FAILED=1
|
|
else
|
|
OFFICE_IDS=$(echo "$OFFICES_BODY" | jq -r '.[].id // empty' 2>/dev/null || true)
|
|
HAS_OFFICE2=""
|
|
for id in $OFFICE_IDS; do
|
|
[ "$id" = "2" ] && HAS_OFFICE2=1
|
|
done
|
|
if [ -z "$HAS_OFFICE2" ]; then
|
|
echo " FAIL: Office 2 not in GET /offices response" >&2
|
|
FAILED=1
|
|
else
|
|
BAD_IDS=""
|
|
for id in $OFFICE_IDS; do
|
|
if [ "$id" != "1" ] && [ "$id" != "2" ]; then
|
|
BAD_IDS="${BAD_IDS} ${id}"
|
|
fi
|
|
done
|
|
if [ -n "$BAD_IDS" ] && [ "$STRICT" = "1" ]; then
|
|
echo " FAIL: Strict mode — office-2 user sees other offices:${BAD_IDS}" >&2
|
|
FAILED=1
|
|
elif [ -n "$BAD_IDS" ]; then
|
|
echo " OK: Office 2 visible (other offices also listed:${BAD_IDS}; set STRICT_OFFICE_LIST=1 to fail)"
|
|
else
|
|
echo " OK: Only offices 1 and 2 visible"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# --- 2. Data isolation: GET /offices/20 (strict: must not return 200 with office 20) ---
|
|
echo "[2] Data isolation: GET /offices/20..."
|
|
OFF20_RESP=$(curl "${CURL_OFFICE2[@]}" "${BASE_URL}/offices/20" 2>/dev/null)
|
|
OFF20_CODE=$(echo "$OFF20_RESP" | tail -n1)
|
|
if [ "$OFF20_CODE" = "200" ]; then
|
|
OFF20_BODY=$(echo "$OFF20_RESP" | sed '$d')
|
|
if echo "$OFF20_BODY" | jq -e '.id == 20' >/dev/null 2>&1; then
|
|
if [ "$STRICT" = "1" ]; then
|
|
echo " FAIL: Strict mode — office-2 user can read office 20 by ID" >&2
|
|
FAILED=1
|
|
else
|
|
echo " OK: 200 with office 20 (set STRICT_OFFICE_LIST=1 to fail)"
|
|
fi
|
|
else
|
|
echo " OK: 200 but no office 20 data"
|
|
fi
|
|
else
|
|
echo " OK: HTTP $OFF20_CODE (access denied or not found)"
|
|
fi
|
|
|
|
# --- 2b. Data isolation: GET /clients?officeId=20 must not return other offices' clients ---
|
|
echo "[3] Data isolation: GET /clients?officeId=20 (must be 403 or empty)..."
|
|
CLIENTS_RESP=$(curl "${CURL_OFFICE2[@]}" "${BASE_URL}/clients?officeId=20" 2>/dev/null)
|
|
CLIENTS_CODE=$(echo "$CLIENTS_RESP" | tail -n1)
|
|
CLIENTS_BODY=$(echo "$CLIENTS_RESP" | sed '$d')
|
|
if [ "$CLIENTS_CODE" = "200" ]; then
|
|
# pageItems or top-level array
|
|
COUNT=$(echo "$CLIENTS_BODY" | jq -r 'if .pageItems then (.pageItems | length) else (if type == "array" then length else 0 end) end' 2>/dev/null || echo "0")
|
|
case "${COUNT:-0}" in
|
|
''|null) COUNT=0 ;;
|
|
esac
|
|
if [ "${COUNT:-0}" -gt 0 ] 2>/dev/null; then
|
|
echo " FAIL: Office-2 user can list clients for office 20 (count=$COUNT)" >&2
|
|
FAILED=1
|
|
else
|
|
echo " OK: No clients for office 20 returned"
|
|
fi
|
|
else
|
|
echo " OK: HTTP $CLIENTS_CODE (access denied or no data)"
|
|
fi
|
|
|
|
# --- 4. Command injection: response must not contain actual file/command output ---
|
|
echo "[4] Command injection: GET response (must not contain file/command output)..."
|
|
INJECT_RESP=$(curl "${CURL_OFFICE2[@]}" "${BASE_URL}/offices?locale=en" 2>/dev/null)
|
|
INJECT_BODY=$(echo "$INJECT_RESP" | sed '$d')
|
|
if echo "$INJECT_BODY" | grep -qE 'root:.*:0:0:|uid=[0-9]+\(.*\)\s+gid='; then
|
|
echo " FAIL: Response may contain command output or file content" >&2
|
|
FAILED=1
|
|
else
|
|
echo " OK: No command/file output in response"
|
|
fi
|
|
|
|
# --- 5. Path traversal: must not return server file content ---
|
|
echo "[5] Path traversal: GET with path-like param (must not return file content)..."
|
|
TRAVERSE_RESP=$(curl "${CURL_OFFICE2[@]}" -G --data-urlencode "dateFormat=../../../etc/passwd" "${BASE_URL}/offices" 2>/dev/null)
|
|
TRAVERSE_BODY=$(echo "$TRAVERSE_RESP" | sed '$d')
|
|
if echo "$TRAVERSE_BODY" | grep -qE 'root:.*:0:0:'; then
|
|
echo " FAIL: Response may contain file content (path traversal)" >&2
|
|
FAILED=1
|
|
else
|
|
echo " OK: No file content in response"
|
|
fi
|
|
|
|
echo ""
|
|
if [ $FAILED -eq 0 ]; then
|
|
echo "All OMNL-2 access security checks passed."
|
|
exit 0
|
|
else
|
|
echo "One or more checks FAILED. Do not treat office-2 access as safe until resolved." >&2
|
|
exit 1
|
|
fi
|