Files
SODEW/sodew-bash-main/libs/commands/malware.sh
T
2026-08-18 09:40:08 +02:00

337 lines
16 KiB
Bash

malwareLabel="[Malware]"
function cmdMalwareFilesDelete() {
local action="${1:-list}"
local quarantinePath="$appDataPath/malware/${appDate}_${appTime}"
local allowedMuPluginFiles="
index.php
00-hosting-loader.php
load.php
hosting-wp-domain-rename.php
burst_rest_api_optimizer.php
elementor-safe-mode.php
mailoptin-customizer-optimizer.php
wgpwpp-cache.php
installatron_hide_status_test.php
"
local total=0
[[ "$action" == "remove" ]] && printTitle "Malware files and blocks (quarantine)" || printTitle "Malware files and blocks (detect)"
run vhostsList error fileList "$olsVhostsPath" d || { printDanger "$error"; return 1; }
while read -r dir; do
local found=0 pluginList wwwPath
wwwPath="$olsVhostsPath/$dir/www"
# mu-plugins: malware (static $a=null) → quarantine
run itemList error fileList "$wwwPath/wp-content/mu-plugins/" f || continue
while read -r item; do
local fullPath="$wwwPath/wp-content/mu-plugins/$item"
if grep -qF '($i){static $a=null' "$fullPath"; then
(( found++ )) || printSection "$dir"
(( total++ ))
if [[ "$action" == "remove" ]]; then
# move mu-plugin file to quarantine
malwareQuarantineMove "$fullPath" && printDotText "$item" "${fontGray}mu-plugin ${fontRed}malware $labelDone" \
|| printDotText "$item" "${fontGray}mu-plugin ${fontRed}malware $labelFail"
# move plugin to quarantine if it exists
local pluginName="${item%.php}"
local pluginDir="$wwwPath/wp-content/plugins/$pluginName"
if [[ -d "$pluginDir" ]]; then
(( total++ ))
malwareQuarantineMove "$pluginDir" && printDotText "/wp-content/plugins/$pluginName" "${fontGray}plugin ${fontRed}malware plugin $labelDone" \
|| printDotText "/wp-content/plugins/$pluginName" "${fontGray}plugin ${fontRed}malware plugin $labelFail"
fi
# create a blocker directory (instead of a file)
# mkdir -p "$wwwPath/wp-content/mu-plugins/$pluginName.php" 2>/dev/null \
# && printDotText "$pluginName.php" "${fontYellow}blocker dir $labelDone" \
# || printDotText "$pluginName.php" "${fontRed}blocker dir $labelFail"
else
printDotText "/wp-content/mu-plugins/$item" "${fontGray}mu-plugin ${fontRed}malware$fontReset"
fi
elif ! listContains "$item" "$allowedMuPluginFiles"; then
(( found++ )) || printSection "$dir"
printDotText "/wp-content/mu-plugins/$item" "$labelUnknown"
fi
done < <(awk 'NF' <<< "$itemList")
# plugins: leftover malware plugin (static $a=null, or a gzip/zip payload disguised as .php) in <name>/<name>.php from old cleanups
run itemList error fileList "$wwwPath/wp-content/plugins/" d || continue
while read -r item; do
local pluginDir="$wwwPath/wp-content/plugins/$item"
local fullPath="$pluginDir/$item.php"
[[ -f "$fullPath" ]] || continue
local magic
magic=$(head -c2 "$fullPath" 2>/dev/null | od -An -tx1 | tr -d ' \n')
if grep -qF '($i){static $a=null' "$fullPath" || [[ "$magic" == "1f8b" || "$magic" == "504b" ]]; then
(( found++ )) || printSection "$dir"
(( total++ ))
if [[ "$action" == "remove" ]]; then
malwareQuarantineMove "$pluginDir" && printDotText "/wp-content/plugins/$item" "${fontGray}plugin gzip ${fontRed}malware $labelDone" \
|| printDotText "/wp-content/plugins/$item" "${fontGray}plugin gzip ${fontRed}malware $labelFail"
else
printDotText "/wp-content/plugins/$item" "${fontGray}plugin gzip ${fontRed}malware$fontReset"
fi
fi
done < <(awk 'NF' <<< "$itemList")
# wp2shell (Auto-login to admin)
pluginList=$(find "$wwwPath/wp-content/plugins" -maxdepth 1 -type d -name 'wp2shell*' 2>/dev/null)
if [ -n "$pluginList" ]; then
while IFS= read -r item; do
(( found++ )) || printSection "$dir"
(( total++ ))
if [[ "$action" == "remove" ]]; then
malwareQuarantineMove "$item" && printDotText "${item#"$wwwPath"}" "${fontGray}plugin ${fontRed}malware $labelDone" \
|| printDotText "${item#"$wwwPath"}" "${fontGray}plugin ${fontRed}malware $labelFail"
else
printDotText "${item#"$wwwPath"}" "${fontGray}plugin ${fontRed}malware$fontReset"
fi
done <<< "$pluginList"
fi
# wp-static-cache (?)
pluginList=$(find "$wwwPath/wp-content/plugins" -maxdepth 1 -type d -name 'wp-static-cache*' 2>/dev/null)
if [ -n "$pluginList" ]; then
while IFS= read -r item; do
(( found++ )) || printSection "$dir"
(( total++ ))
if [[ "$action" == "remove" ]]; then
malwareQuarantineMove "$item" && printDotText "${item#"$wwwPath"}" "${fontRed}malware $labelDone" \
|| printDotText "${item#"$wwwPath"}" "${fontRed}malware $labelFail"
else
printDotText "${item#"$wwwPath"}" "${fontRed}malware$fontReset"
fi
done <<< "$pluginList"
fi
# hex-named php/zip в wp-content, wp-content/cache, themes/*, uploads/**/
local hexZipList
hexZipList=$(
find "$wwwPath/wp-content" -maxdepth 1 -type f -regextype posix-extended -regex '.*/\.?[0-9a-f]{8}\.(php|zip)$' 2>/dev/null
find "$wwwPath/wp-content/cache" -maxdepth 1 -type f -regextype posix-extended -regex '.*/\.?[0-9a-f]{8}\.(php|zip)$' 2>/dev/null
find "$wwwPath/wp-content/themes" -maxdepth 2 -type f -regextype posix-extended -regex '.*/\.?[0-9a-f]{8}\.(php|zip)$' 2>/dev/null
find "$wwwPath/wp-content/uploads" -maxdepth 3 -type f -regextype posix-extended -regex '.*/\.?[0-9a-f]{8}\.(php|zip)$' 2>/dev/null
)
if [ -n "$hexZipList" ]; then
while IFS= read -r item; do
(( found++ )) || printSection "$dir"
(( total++ ))
if [[ "$action" == "remove" ]]; then
malwareQuarantineMove "$item" && printDotText "${item#"$wwwPath"}" "${fontGray}php/zip ${fontRed}malware $labelDone" \
|| printDotText "${item#"$wwwPath"}" "${fontGray}php/zip ${fontRed}malware $labelFail"
else
printDotText "${item#"$wwwPath"}" "${fontGray}php/zip ${fontRed}malware$fontReset"
fi
done <<< "$hexZipList"
fi
# wp-content/themes/*/functions.php cut out block SC_TH_BEGIN...SC_TH_END
local funcList
funcList=$(find "$wwwPath/wp-content/themes" -maxdepth 2 -name "functions.php" 2>/dev/null)
if [ -n "$funcList" ]; then
while IFS= read -r item; do
if grep -qF '/* SC_TH_BEGIN:' "$item" 2>/dev/null; then
(( found++ )) || printSection "$dir"
(( total++ ))
if [[ "$action" == "remove" ]]; then
# keep the original file in quarantine, then cut the block out
malwareQuarantineCopy "$item" \
&& sed -i '/\/\* SC_TH_BEGIN:/,/SC_TH_END:[^*]*\*\//d' "$item" \
&& printDotText "${item#"$wwwPath"}" "${fontGray}SC_TH block ${fontRed}malware $labelDone" \
|| printDotText "${item#"$wwwPath"}" "${fontGray}SC_TH block ${fontRed}malware $labelFail"
else
printDotText "${item#"$wwwPath"}" "${fontGray}SC_TH block ${fontRed}malware$fontReset"
fi
fi
done <<< "$funcList"
fi
# wp-content/advanced-cache.php cut out block SC_ADV_BEGIN...SC_ADV_END
local advCacheFile="$wwwPath/wp-content/advanced-cache.php"
if grep -qF '/* SC_ADV_BEGIN:' "$advCacheFile" 2>/dev/null; then
(( found++ )) || printSection "$dir"
(( total++ ))
if [[ "$action" == "remove" ]]; then
# keep the original file in quarantine, then cut the block out
malwareQuarantineCopy "$advCacheFile" \
&& sed -i '/\/\* SC_ADV_BEGIN:/,/SC_ADV_END:[^*]*\*\//d' "$advCacheFile" \
&& printDotText "${advCacheFile#"$wwwPath"}" "${fontGray}SC_ADV block ${fontRed}malware $labelDone" \
|| printDotText "${advCacheFile#"$wwwPath"}" "${fontGray}SC_ADV block ${fontRed}malware $labelFail"
else
printDotText "${advCacheFile#"$wwwPath"}" "${fontGray}SC_ADV block ${fontRed}malware$fontReset"
fi
fi
# wp-content/db.php cut out block SC_DB_BEGIN...SC_DB_END
local dbFile="$wwwPath/wp-content/db.php"
if grep -qF '/* SC_DB_BEGIN:' "$dbFile" 2>/dev/null; then
(( found++ )) || printSection "$dir"
(( total++ ))
if [[ "$action" == "remove" ]]; then
# keep the original file in quarantine, then cut the block out
malwareQuarantineCopy "$dbFile" \
&& sed -i '/\/\* SC_DB_BEGIN:/,/SC_DB_END:[^*]*\*\//d' "$dbFile" \
&& printDotText "${dbFile#"$wwwPath"}" "${fontGray}SC_DB block ${fontRed}malware $labelDone" \
|| printDotText "${dbFile#"$wwwPath"}" "${fontGray}SC_DB block ${fontRed}malware $labelFail"
else
printDotText "${dbFile#"$wwwPath"}" "${fontGray}SC_DB block ${fontRed}malware$fontReset"
fi
fi
# other
# for subdir in wp-content/mu-plugins wp-content/plugins; do
# pluginList=$(find "$wwwPath/$subdir" -maxdepth 1 -regextype posix-extended -regex '^.*[^0-9a-f][0-9a-f]{6,8}(\.php)?$' 2>/dev/null)
# if [ -n "$pluginList" ]; then
# while IFS= read -r item; do
# (( found++ )) || printSection "$dir"
# printDotText "${item#"$wwwPath"}" "${fontYellow}warning$fontReset"
# done <<< "$pluginList"
# fi
# done
done < <(awk 'NF' <<< "$vhostsList")
printRow
printDotText "Total" "$total"
}
function cmdMalwareUserDelete() {
local action="${1:-list}"
[[ "$action" == "remove" ]] && printTitle "Users (deleting)" || printTitle "Users (detect)"
local output error
if ! run output error malwareUserList; then
printDanger "$error"
return 1
fi
local total=0
if [[ "$action" == "remove" ]]; then
# group user_id on db_name+table_name
declare -A ids_map
declare -A name_map
while IFS=$'\t' read -r db_name user_id user_login user_registered table_name; do
[[ -z "$db_name" ]] && continue
(( total++ ))
local key="${db_name}|${table_name}"
ids_map[$key]+="${user_id},"
name_map[$key]="$db_name | $table_name"
done < <(awk 'NF' <<< "$output")
# delete with a single query on the table
for key in "${!ids_map[@]}"; do
local ids="${ids_map[$key]%,}"
local db_name="${key%%|*}"
local table_name="${key##*|}"
local sql="DELETE FROM \`${db_name}\`.\`${table_name}\` WHERE ID IN (${ids});"
local qout qerr
if run qout qerr mariadbMasterRootQuery "$sql"; then
printDotText "${name_map[$key]}" "${ids} $labelDone"
else
printDotText "${name_map[$key]}" "${ids} $labelFail"
printDanger "$qerr"
fi
done
else
while IFS=$'\t' read -r db_name user_id user_login user_registered table_name; do
[[ -z "$db_name" ]] && continue
(( total++ ))
printDotText "${db_name} | ${user_login} (${user_id})" "$labelDanger"
done < <(awk 'NF' <<< "$output")
fi
printDotText "Total" "$total"
}
function cmdMalwareOptionsDelete() {
local action="${1:-list}"
[[ "$action" == "remove" ]] && printTitle "Options (deleting)" || printTitle "Options (detect)"
local output error
if ! run output error malwareOptionsList; then
printDanger "$error"
return 1
fi
local total=0
if [[ "$action" == "remove" ]]; then
# group option_id by db_name+table_name
declare -A ids_map
declare -A name_map
while IFS=$'\t' read -r db_name table_name option_id option_name; do
[[ -z "$db_name" ]] && continue
(( total++ ))
local key="${db_name}|${table_name}"
ids_map[$key]+="${option_id},"
name_map[$key]="$db_name | $table_name"
done < <(awk 'NF' <<< "$output")
# delete with a single query per table
for key in "${!ids_map[@]}"; do
local ids="${ids_map[$key]%,}"
local db_name="${key%%|*}"
local table_name="${key##*|}"
local sql="DELETE FROM \`${db_name}\`.\`${table_name}\` WHERE option_id IN (${ids});"
local qout qerr
if run qout qerr mariadbMasterRootQuery "$sql"; then
printDotText "${name_map[$key]}" "${ids} $labelDone"
else
printDotText "${name_map[$key]}" "${ids} $labelFail"
printDanger "$qerr"
fi
done
else
while IFS=$'\t' read -r db_name table_name option_id option_name; do
[[ -z "$db_name" ]] && continue
(( total++ ))
printDotText "${db_name}" "${option_id}: ${option_name} $labelDanger"
done < <(awk 'NF' <<< "$output")
fi
printDotText "Total" "$total"
}
function cmdMalwareCronDelete() {
local action="${1:-list}"
[[ "$action" == "remove" ]] && printTitle "Cron (deleting)" || printTitle "Cron (detect)"
local output error
if ! run output error malwareCronList; then
printDanger "$error"
return 1
fi
local total=0
while IFS=$'\t' read -r db_name table_name option_id option_name; do
[[ -z "$db_name" ]] && continue
(( total++ ))
if [[ "$action" == "remove" ]]; then
# remove only 'sc_cron_fetch' entry from the serialized cron array | pattern: i:TIMESTAMP;a:N:{s:13:"sc_cron_fetch";...}}}
# local sql="UPDATE \`${db_name}\`.\`${table_name}\`
# SET option_value = REGEXP_REPLACE(
# option_value,
# 'i:[0-9]+;a:1:\\\\{s:13:\"sc_cron_fetch\";a:1:\\\\{[^}]+\\\\}\\\\}\\\\}',
# ''
# )
# WHERE option_id = ${option_id};"
# local sql="UPDATE \`${db_name}\`.\`${table_name}\`
# SET option_value = REGEXP_REPLACE(
# option_value,
# 'i:[0-9]+;a:1:\\\\{s:13:\"sc_cron_fetch\";a:1:\\\\{s:[0-9]+:\"[^\"]+\";a:[0-9]+:\\\\{[^}]*\\\\}\\\\}\\\\}\\\\}',
# ''
# )
# WHERE option_id = ${option_id};"
local sql="UPDATE \`${db_name}\`.\`${table_name}\` SET option_value = REPLACE(option_value, 's:13:\"sc_cron_fetch\"', 's:16:\"sc_cron_fetch_dis\"') WHERE option_id = ${option_id};"
local qout qerr
if run qout qerr mariadbMasterRootQuery "$sql"; then
printDotText "${db_name}" "sc_cron_fetch $labelDone"
else
printDotText "${db_name}" "sc_cron_fetch $labelFail"
printDanger "$qerr"
fi
else
printDotText "${db_name}" "sc_cron_fetch $labelDanger"
fi
done < <(awk 'NF' <<< "$output")
printDotText "Total" "$total"
}