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 /.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" }