337 lines
16 KiB
Bash
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"
|
|
}
|