Commit 78591556 authored by 易初's avatar 易初 🖐🏻

Update incident_containment_cleanup_v1.sh

parent b0862410
......@@ -15,7 +15,7 @@ set -u
umask 077
export LC_ALL=C
VERSION="1.0.1"
VERSION="1.1.0"
APPLY=0
BLOCK_IOCS=1
REMOVE_KEYS=1
......@@ -42,6 +42,11 @@ Options:
--ack-unload-risk Acknowledge possible kernel panic/network disruption
-h, --help Show this help
Credential safety:
The script never changes passwords, deletes unknown historical SSH keys, or
rewrites sshd_config. It records mandatory manual actions in plan.tsv so an
operator can preserve a tested administrative access path and avoid lockout.
Safe first run:
bash incident_containment_cleanup_v1.sh --output /root
......@@ -144,14 +149,14 @@ capture_state() {
grep -E '^(ata_linkq|nvme_hctxq|xen_gntq|YL4Qr|9mLHs|3ja8C|8bmOn)[[:space:]]' /proc/modules 2>/dev/null || true
echo '===== suspicious processes ====='
ps -ef 2>/dev/null | grep -E \
'[A]liyunSysAssist|[i]rqbalance|[r]ngd|/tmp/[d]bg|nvme_sqmap|scsi_cmdq|usb_portctl' || true
'[A]liyunSysAssist|[i]rqbalance|[r]ngd|/(tmp|var/tmp|var/opt|usr/local/sbin)/[d]bg|nvme_sqmap|scsi_cmdq|usb_portctl' || true
echo '===== suspicious sockets ====='
if have ss; then
ss -anp 2>/dev/null | grep -E \
'hpgoc2|101\.201\.148\.142|8\.217\.173\.211|AliyunSysAssist|irqbalance|rngd' || true
'hpgoc2|101\.201\.148\.142|8\.217\.173\.211|47\.237\.187\.64|39\.108\.93\.196|AliyunSysAssist|irqbalance|rngd' || true
elif have netstat; then
netstat -anp 2>/dev/null | grep -E \
'hpgoc2|101\.201\.148\.142|8\.217\.173\.211|AliyunSysAssist|irqbalance|rngd' || true
'hpgoc2|101\.201\.148\.142|8\.217\.173\.211|47\.237\.187\.64|39\.108\.93\.196|AliyunSysAssist|irqbalance|rngd' || true
fi
echo '===== suspect files ====='
for path in \
......@@ -159,6 +164,8 @@ capture_state() {
/usr/lib/libnuma_hint.so /usr/lib/libcpu_balance.so \
/usr/local/share/aliyun-sys-assist/AliyunSysAssist \
/usr/sbin/aliyun-sys-assist-setup \
/tmp/dbg /tmp/dbg-el6 /var/tmp/dbg /var/opt/dbg /usr/local/sbin/dbg \
/tmp/sys.rpm /tmp/elf.rpm /tmp/kd.rpm \
/usr/lib/nvme_sqmap /usr/lib/scsi_cmdq /usr/lib/usb_portctl \
/etc/modules-load.d/ata_linkq.conf /etc/modules-load.d/nvme_hctxq.conf \
/etc/modules-load.d/xen_gntq.conf /etc/sysconfig/modules/vmware-tools.modules
......@@ -169,6 +176,18 @@ capture_state() {
done
echo '===== package verification ====='
have rpm && rpm -V irqbalance aliyun-sys-assist ata_linkq nvme_hctxq xen_gntq 2>&1 || true
have rpm && rpm -V kernel-devel elfutils-libelf-devel 2>&1 || true
echo '===== effective SSH policy ====='
if have sshd; then
sshd -T 2>/dev/null | grep -Ei \
'^(permitrootlogin|passwordauthentication|pubkeyauthentication|authorizedkeyscommand|authorizedkeysfile)' || true
fi
grep -nE '^[[:space:]]*#?[[:space:]]*(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|AuthorizedKeysCommand|AuthorizedKeysCommandUser|AuthorizedKeysCommandRunAs)' \
/etc/ssh/sshd_config 2>/dev/null || true
echo '===== SSH private-key inventory ====='
find /root /home -xdev -type f \
\( -name id_rsa -o -name id_dsa -o -name id_ecdsa -o -name id_ed25519 \) \
-exec stat {} \; 2>/dev/null || true
echo '===== firewall ====='
have iptables-save && iptables-save 2>/dev/null || true
} > "$output" 2>&1
......@@ -190,12 +209,17 @@ backup_copy() {
}
quarantine_path() {
local source destination digest
local source destination digest attrs
source="$1"
test -e "$source" || test -L "$source" || return 0
destination="$QUARANTINE/files$source"
digest=""
test -f "$source" && digest="$(hash_file "$source")"
attrs=""
if have lsattr; then
attrs="$(lsattr -d "$source" 2>/dev/null || true)"
printf 'attributes\t%s\t%s\n' "$source" "$(sanitize "$attrs")" >> "$MANIFEST"
fi
if test "$APPLY" -ne 1; then
result quarantine "$source" planned "sha256=$digest destination=$destination"
return 0
......@@ -205,6 +229,18 @@ quarantine_path() {
printf '%s\t%s\t%s\n' "$digest" "$source" "$destination" >> "$MANIFEST"
result quarantine "$source" applied "sha256=$digest destination=$destination"
log "quarantined $source"
elif test -f "$source" && printf '%s' "$attrs" | grep -Eq -- '[-a-zA-Z]*i[-a-zA-Z]*[[:space:]]'; then
# Exact campaign files were sometimes made immutable. Preserve the original
# attribute string above, then clear only the immutable bit for this file.
if have chattr; then chattr -i "$source" >> "$LOG" 2>&1 || true; fi
if mv "$source" "$destination" >> "$LOG" 2>&1; then
printf '%s\t%s\t%s\n' "$digest" "$source" "$destination" >> "$MANIFEST"
result quarantine "$source" applied "immutable bit cleared; sha256=$digest destination=$destination"
log "cleared immutable bit and quarantined $source"
else
have chattr && chattr +i "$source" >> "$LOG" 2>&1 || true
result quarantine "$source" failed "destination=$destination; immutable bit restored when possible"
fi
else
result quarantine "$source" failed "destination=$destination"
fi
......@@ -251,7 +287,7 @@ block_ip() {
fi
}
KNOWN_BLOCK_IPS="101.201.148.142 8.217.173.211${EXTRA_BLOCK_IPS:+ $EXTRA_BLOCK_IPS}"
KNOWN_BLOCK_IPS="101.201.148.142 8.217.173.211 47.237.187.64 39.108.93.196${EXTRA_BLOCK_IPS:+ $EXTRA_BLOCK_IPS}"
for address in $KNOWN_BLOCK_IPS; do
block_ip "$address"
done
......@@ -264,7 +300,7 @@ do
disable_service "$service"
done
KNOWN_PROCESS_HASHES='aa0eb857cbb40a1516ba0f6ac33d2265d0b49e9067abeec4264057193e440e09 87f629925728922e2b092036b214b364b2419452a514d0b44f033ebe4a0d873c a3d832da2bd83bff45fb1df576de814f57b004777ea0dd7d1beaa46340d8ca49 79b10ffb3c14b7745d7a7eb5e90c708b2d5eca52fe2ff65106ba4f391c02eacd 8ca077298ca5a46655be209bd993268e5e224f0bc10a079825e023e036d342fe'
KNOWN_PROCESS_HASHES='aa0eb857cbb40a1516ba0f6ac33d2265d0b49e9067abeec4264057193e440e09 87f629925728922e2b092036b214b364b2419452a514d0b44f033ebe4a0d873c a3d832da2bd83bff45fb1df576de814f57b004777ea0dd7d1beaa46340d8ca49 79b10ffb3c14b7745d7a7eb5e90c708b2d5eca52fe2ff65106ba4f391c02eacd 8ca077298ca5a46655be209bd993268e5e224f0bc10a079825e023e036d342fe 0e1bd634d5fc8f4df1c91f0072a4556eb11f41ecc95b05f5a2ffb0e0b914613d 0eb8af527d40c3a1e27293be935504b6201db991fed028c6e4b805dca7f9f15d ca3c6256354a8053812c85feff0c540b6e2feddfc8fc7804ef2637628e9426e8 ca90137ec7f88f9426e2a0b591d125b6ee20f75b30e4fd9445cdbbf402594a57'
is_known_process_hash() {
case " $KNOWN_PROCESS_HASHES " in
......@@ -277,7 +313,10 @@ is_known_process_path() {
case "$1" in
/usr/local/share/aliyun-sys-assist/*|/usr/lib/aliyun-sys-assist-payload/*|\
/usr/lib/nvme_sqmap|/usr/lib/scsi_cmdq|/usr/lib/usb_portctl|\
'/tmp/dbg (deleted)'|/tmp/dbg) return 0 ;;
'/tmp/dbg (deleted)'|'/tmp/dbg-el6 (deleted)'|\
'/var/tmp/dbg (deleted)'|'/var/opt/dbg (deleted)'|\
'/usr/local/sbin/dbg (deleted)'|\
/tmp/dbg|/tmp/dbg-el6|/var/tmp/dbg|/var/opt/dbg|/usr/local/sbin/dbg) return 0 ;;
*) return 1 ;;
esac
}
......@@ -371,6 +410,50 @@ remove_campaign_keys() {
remove_campaign_keys
record_manual_credential_actions() {
local effective risky keyfile keyinfo accepted_159
effective=""
risky=""
if have sshd; then
effective="$(sshd -T 2>/dev/null || true)"
risky="$(printf '%s\n' "$effective" \
| grep -Ei '^(permitrootlogin[[:space:]]+yes|passwordauthentication[[:space:]]+yes)' \
| tr '\n' ' ')"
fi
if test -n "$risky"; then
result ssh_policy /etc/ssh/sshd_config manual_required \
"effective risky settings: $risky; establish and test a trusted non-root key before hardening"
else
result ssh_policy /etc/ssh/sshd_config review_required \
'compare PermitRootLogin, PasswordAuthentication and AuthorizedKeysCommand against the trusted baseline'
fi
result credential root manual_required \
'rotate the root password from a trusted channel; the stolen cloud credential reset root passwords during the incident'
result cloud_credential account manual_required \
'disable the compromised AccessKey and active sessions; require MFA and least-privilege RAM/STS credentials'
find /root /home -xdev -type f \
\( -name id_rsa -o -name id_dsa -o -name id_ecdsa -o -name id_ed25519 \) \
-print 2>/dev/null | while IFS= read -r keyfile; do
keyinfo=""
if test -r "${keyfile}.pub" && have ssh-keygen; then
keyinfo="$(ssh-keygen -lf "${keyfile}.pub" 2>/dev/null || true)"
fi
result ssh_private_key "$keyfile" manual_required \
"rotate/revoke downstream trust; all private keys on a root-compromised host are exposed; public_fingerprint=$keyinfo"
done
accepted_159="$(zgrep -hE 'Accepted (publickey|password).* from 172\.18\.172\.159 ' \
/var/log/secure* 2>/dev/null | head -8 || true)"
if test -n "$accepted_159"; then
result ssh_lateral_trust 172.18.172.159 manual_required \
"successful inbound authentication was recorded; identify the accepted key and revoke the same trust across downstream hosts: $accepted_159"
fi
}
record_manual_credential_actions
# Remove malicious library preload references while preserving unrelated lines.
neutralize_ld_preload() {
local file temp mode uid gid
......@@ -423,6 +506,14 @@ for path in \
/usr/lib/usb_portctl \
/tmp/dbg \
/tmp/dbg-el6 \
/var/tmp/dbg \
/var/opt/dbg \
/usr/local/sbin/dbg \
/tmp/sys.rpm \
/tmp/elf.rpm \
/tmp/kd.rpm \
/tmp/v2_rpm.log \
/tmp/v2_rpm.exit \
/etc/init.d/aliyun-sys-assist \
/etc/rc.d/init.d/aliyun-sys-assist \
/usr/lib/systemd/system/aliyun-sys-assist.service \
......@@ -431,6 +522,18 @@ do
quarantine_path "$path"
done
# Exact temporary/staging names recovered from the attacker's own cleanup
# commands. Dry-run remains the default, and APPLY moves them recoverably.
for path in \
/tmp/.irq-stats-* /dev/shm/.hpgoc2_skag_* /dev/shm/.ov2* \
/tmp/CVE-2021-4034* /tmp/.pwn* /tmp/.k42* /var/opt/.k42* \
/tmp/.fs_i.sh /var/tmp/.fs_i.sh /tmp/.ht_* /var/tmp/.ht_* \
/tmp/getsshpwd /tmp/.getsshpwd /var/opt/getsshpwd \
/tmp/ssh4.zip /tmp/c.tar.gz /tmp/.chlp*; do
test -e "$path" || test -L "$path" || continue
quarantine_path "$path"
done
# Quarantine irqbalance only when its executable hash is one of the confirmed
# malicious variants. A legitimate binary is left untouched.
if test -f /usr/sbin/irqbalance; then
......@@ -516,6 +619,7 @@ capture_state "$AFTER"
REMAINING_MODULES="$(grep -Ec '^(ata_linkq|nvme_hctxq|xen_gntq|YL4Qr|9mLHs|3ja8C|8bmOn)[[:space:]]' /proc/modules 2>/dev/null || true)"
FAILED_ACTIONS="$(awk -F '\t' 'NR > 1 && $3 == "failed" {n++} END {print n+0}' "$PLAN")"
APPLIED_ACTIONS="$(awk -F '\t' 'NR > 1 && $3 == "applied" {n++} END {print n+0}' "$PLAN")"
MANUAL_ACTIONS="$(awk -F '\t' 'NR > 1 && ($3 == "manual_required" || $3 == "review_required") {n++} END {print n+0}' "$PLAN")"
{
echo "Host: $HOST_NAME"
......@@ -525,6 +629,7 @@ APPLIED_ACTIONS="$(awk -F '\t' 'NR > 1 && $3 == "applied" {n++} END {print n+0}'
echo "Output: $RUN_DIR"
echo "Applied actions: $APPLIED_ACTIONS"
echo "Failed actions: $FAILED_ACTIONS"
echo "Manual/review actions: $MANUAL_ACTIONS"
echo "Campaign modules still visible as loaded: $REMAINING_MODULES"
echo
if test "$APPLY" -ne 1; then
......@@ -533,6 +638,8 @@ APPLIED_ACTIONS="$(awk -F '\t' 'NR > 1 && $3 == "applied" {n++} END {print n+0}'
echo 'Assessment: PARTIALLY CONTAINED; HOST REMAINS KERNEL-COMPROMISED'
elif test "$FAILED_ACTIONS" -gt 0; then
echo 'Assessment: CONTAINMENT ATTEMPTED WITH FAILURES; REVIEW REQUIRED'
elif test "$MANUAL_ACTIONS" -gt 0; then
echo 'Assessment: USERLAND CONTAINMENT APPLIED; MANDATORY CREDENTIAL/SSH ACTIONS REMAIN'
else
echo 'Assessment: CAMPAIGN COMPONENTS NEUTRALIZED AS FAR AS THIS HOST CAN REPORT; REBUILD STILL REQUIRED'
fi
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment