2 Commits

1 changed files with 387 additions and 275 deletions
Unified View
  1. +387
    -275
      tools/archrisks.sh

+ 387
- 275
tools/archrisks.sh View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# archrisks - Get security risk severity & count of installed packages on Arch Linux # archrisks - Get security risk severity & count of installed packages on Arch Linux
# Copyright (C) 2021 Pekka Helenius
# Copyright (C) 2021,2024 Pekka Helenius
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -53,318 +53,430 @@ ARCH_MANAGERS=(
# [13,spinach]="-Syy|-Qi|-Q|-Si|no_root" # [13,spinach]="-Syy|-Qi|-Q|-Si|no_root"
# [14,trizen]="-Syy|-Qi|-Q|-Si|no_root" # [14,trizen]="-Syy|-Qi|-Q|-Si|no_root"
) )
SELECTED_MANAGER=
MANAGER_PRIORITY_LOWLIMIT=-1
priority_lowlimit=-1
default_order="level"
default_reverse=0
SORT_ORDER="level"
SORT_REVERSE=0
provider="security.archlinux.org"
NETWORK_HOST_ENDPOINT="security.archlinux.org"
input_count=${#@}
[[ "${input_count}" -eq 1 ]] && input_1="${1}"
[[ "${input_count}" -eq 2 ]] && input_1="${1}"; input_2="${2}"
usage() {
echo -e "
Usage: $0
-h|--help
1st arg: --sort=<name,issues,level,status,desc> (optional)
2nd arg: --reverse (optional)
"
exit 0
}
input_parser() {
if \
[[ "${input_count}" -gt 2 ]] || \
[[ "${input_1}" == "-h" ]] || \
[[ "${input_1}" == "--help" ]]
then
usage
elif [[ "${input_count}" -ne 0 ]]
then
SORT_ORDER=$(echo "${input_1}" | sed -r 's/^\-\-sort=(.*)/\1/')
case "${SORT_ORDER}" in
name|issues|level|version|desc)
echo "Custom sort order selected: ${SORT_ORDER}"
;;
*)
echo "Unknown sorting order selected (${SORT_ORDER})."
usage
esac
if [[ "${input_count}" -eq 2 ]]
then
case "${input_2}" in
"--reverse")
echo "Reverse ordering"
SORT_REVERSE=1
;;
*)
echo "Unknown option '${input_2}'"
SORT_REVERSE=0
esac
fi
fi
}
connection_test() {
local host_endpoint
input_count=${#@}
[[ $input_count -eq 1 ]] && input_1=${1}
[[ $input_count -eq 2 ]] && input_1=${1}; input_2=${2}
host_endpoint="${1}"
function helpCaller() {
echo -e "
Usage: $0
-h|--help
1st arg: --sort=<name,issues,level,status,desc> (optional)
2nd arg: --reverse (optional)
"
if [[ $(ping -c 1 "${host_endpoint}" 2>&1 | grep -c "Name or service not known") -ne 0 ]]
then
echo -e "\nCan't connect to $host_endpoint. Please check your internet connection and try again.\n"
exit 0 exit 0
fi
} }
function inputParser() {
if [[ ${input_count} -gt 2 ]] || [[ ${input_1} == "-h" ]] || [[ ${input_1} == "--help" ]]; then
helpCaller
elif [[ ${input_count} -eq 0 ]]; then
sort_order=${default_order}
sort_reverse=${default_reverse}
else
sort_order=$(echo ${input_1} | sed -r 's/^\-\-sort=(.*)/\1/')
case ${sort_order} in
name|issues|level|version|desc)
echo "Custom sort order selected: ${sort_order}"
;;
*)
echo "Unknown sorting order selected (${sort_order})."
helpCaller
esac
if [[ ${input_count} -eq 2 ]]; then
case ${input_2} in
"--reverse")
echo "Reverse ordering"
sort_reverse=1
;;
*)
echo "Unknown option '${input_2}'"
sort_reverse=${default_reverse}
esac
fi
function find_my_package_manager() {
local i
local managers_list
local managers_priority_list
local manager_priority
local manager
i=0
managers_list=()
managers_priority_list=()
for manager_str in ${!ARCH_MANAGERS[@]}; do
OLDIFS=${IFS}
IFS=","
manager_array=(${manager_str})
IFS=${OLDIFS}
manager_priority="${manager_array[0]}"
manager="${manager_array[1]}"
if [[ "${manager_priority}" -lt "${MANAGER_PRIORITY_LOWLIMIT}" ]]
then
echo "Minimum priority is $((${MANAGER_PRIORITY_LOWLIMIT} + 1)). You have a package which has lower priority value. Exiting."
exit 1
fi fi
}
function internetTest() {
if [[ $(ping -c 1 $provider 2>&1 | grep -c "Name or service not known") -ne 0 ]]; then
echo -e "\nCan't connect to $provider. Please check your internet connection and try again.\n"
exit 0
if [[ $(type -P ${manager}) ]]
then
managers_list[$i]="${manager}"
managers_priority_list[$i]=${manager_priority}
let i++
fi
done
if [[ ${#managers_list[@]} -eq 0 ]]
then
echo "Not any valid package manager found. Exiting."
exit 1
fi
if [[ $(echo ${managers_priority_list[@]} | tr ' ' '\n' | uniq -d | wc -l) -ne 0 ]]
then
echo "Package managers with same priority found. Check internal manager list for duplicates. Exiting."
exit 1
fi
# Select package manager by priority. Highest is selected.
i=0
while [[ "${i}" -le $((${#managers_list[@]} - 1)) ]]
do
if [[ ${managers_priority_list[i]} -gt ${priority_lowlimit} ]]
then
priority_lowlimit=${managers_priority_list[i]}
SELECTED_MANAGER=${managers_list[i]}
fi fi
let i++
done
OLDIFS=${IFS}
IFS="|"
pkg_command=(${ARCH_MANAGERS["$priority_lowlimit,$SELECTED_MANAGER"]})
IFS=${OLDIFS}
command_refresh="${pkg_command[0]}"
command_pkginfo_local="${pkg_command[1]}"
command_pkginfo_local_short="${pkg_command[2]}"
command_pkginfo_remote="${pkg_command[3]}"
command_require_root="${pkg_command[4]}"
if [[ "${command_require_root}" == "root" ]]
then
if [[ ! $(id -u) -eq 0 ]]
then
echo -e "\nThis command requires root privileges.\n"
exit 0
fi
fi
} }
function findMyPackageManager() {
# TODO: We can't really depend on parsing output strings since they vary between Arch package managers
package_version_parsed() {
echo "${1}" | awk -F ' ' '{print $2}' | sed -r 's/[a-z]+.*//; s/[:_+-]/\./g; s/[^0-9]$//;'
}
i=0
for managerStr in ${!ARCH_MANAGERS[@]}; do
package_version_check() {
local system_version
local repo_version
local version_array_1
local version_array_2
local first_version_numbers
local last_version_numbers
local comparables
local version_status_msg
local check1
local check2
local s
# Expected output syntax: "^<string> <version number>$"
# TODO: We can't really depend on parsing output strings since they vary between Arch package managers
system_version=$(${SELECTED_MANAGER} ${command_pkginfo_local_short} "${1}")
repo_version=$(${SELECTED_MANAGER} ${command_pkginfo_remote} $1 | grep -E "^Version\s*:" | sed -r 's/.*(:\s*.*$)/\1/')
version_array_1=$(package_version_parsed "${system_version}")
version_array_2=$(package_version_parsed "${repo_version}")
#Count of version elements (0 18 2 1 contains 4 numbers, for example)
first_version_numbers=$(echo "${version_array_1}" | awk -F '.' '{print split($0, a)}')
last_version_numbers=$(echo "${version_array_2}" | awk -F '.' '{print split($0, a)}')
# Count of comparable version elements (maximum)
# We compare this much of elements, not more
if [[ "${last_version_numbers}" -lt "${first_version_numbers}" ]]
then
comparables="${last_version_numbers}"
else
comparables="${first_version_numbers}"
fi
# If all numbers are same, we don't analyze them more deeply.
if [[ "${version_array_1}" == "${version_array_2}" ]]
then
version_status_msg="${green}Package is updated"
else
s=1
while [ ${s} -le ${comparables} ]
do
check1=$(echo -e "${version_array_1}" | awk -v var=$s -F '.' '{print $var}')
check2=$(echo -e "${version_array_2}" | awk -v var=$s -F '.' '{print $var}')
if [[ ${check2} -gt ${check1} ]]
then
# Repo number is greater
version_status_msg="${yellow}Update available"
break
elif [[ ${check2} -lt ${check1} ]]
then
# System number is greater
version_status_msg="${reset}Newer package installed"
break
fi
let s++
done
fi
if [[ -z "${version_status_msg}" ]]
then
version_status_msg="${reset}Unknown"
fi
echo "${version_status_msg}"
}
manager_priority=$(echo ${managerStr} | awk -F ',' '{print $1}')
manager=$(echo ${managerStr} | awk -F ',' '{print $2}')
exec_tool() {
if [[ ${manager_priority} -lt ${priority_lowlimit} ]]; then
echo "Minimum priority is $((${priority_lowlimit} + 1)). You have a package which has lower priority value. Exiting."
exit 1
fi
local i
local package_count
local risks_parsed_count
local risks
local description_column_max_chars
if [[ $(echo $(which ${manager} &>/dev/null)$?) -eq 0 ]]; then
managers_list[$i]=${manager}
managers_priority_list[$i]=${manager_priority}
let i++
fi
done
local r_package_name
local r_package_security_issues_count
local r_package_security_issues_level
local r_package_security_issues_level
local r_package_description
if [[ ${#managers_list[@]} -eq 0 ]]; then
echo "Not any valid package manager found. Exiting."
exit 1
fi
local risk_entries
if [[ $(echo ${managers_priority_list[@]} | tr ' ' '\n' | uniq -d | wc -l) -ne 0 ]]; then
echo "Package managers with same priority found. Check internal manager list for duplicates. Exiting."
exit 1
fi
local sort_params
local sort_column
# Select package manager by priority. Highest is selected.
i=0
while [[ ${i} -le $((${#managers_list[@]} - 1)) ]]; do
if [[ ${managers_priority_list[i]} -gt ${priority_lowlimit} ]]; then
priority_lowlimit=${managers_priority_list[i]}
selected_manager=${managers_list[i]}
fi
let i++
local package_alert_importance_status
local package_alert_msg_color
local package_alert_importance_output_status
local package_security_issues_count
local security_issues_package_summary
local security_issues_total_count
local security_msg_color
i=1
description_column_max_chars=35
sort_params=()
input_parser
connection_test "${NETWORK_HOST_ENDPOINT}"
find_my_package_manager
echo "Security report date: $(date '+%d-%m-%Y, %X') (TZ: $(timedatectl status | grep "Time zone:" | awk '{print $3}'))"
echo -e "\nSynchronizing package databases with ${SELECTED_MANAGER}\n"
${SELECTED_MANAGER} ${command_refresh} || exit
if [[ ! $(type -P arch-audit) ]]
then
echo -e "\nCouldn't find Arch Linux security utility (arch-audit) in \$PATH. Please make sure it's installed.\n"
else
packages=
package_count=0
risks_parsed_count=0
IFS=$'\n'
for au in $(arch-audit); do
package_name=$(echo "${au}" | awk -F ' ' '{print $1}')
risk_level=$(echo "${au}" | grep -oE "Low|Medium|High|Critical")
risks_count=$(echo "${au}" | grep -oP "(?<=by ).+(?=\. )" | sed 's/, /\n/g' | wc -l)
risks[$package_count]="$package_name;$risk_level;$risks_count"
packages="${packages}, ${package_name}"
let package_count++
done done
pkg_command=${ARCH_MANAGERS["$priority_lowlimit,$selected_manager"]}
echo -e "Analyzing ${#risks[*]} vulnerable packages. This takes a while...\n"
command_refresh=$(echo $pkg_command | awk -F '|' '{print $1}')
command_pkginfo_local=$(echo $pkg_command | awk -F '|' '{print $2}')
command_pkginfo_local_short=$(echo $pkg_command | awk -F '|' '{print $3}')
command_pkginfo_remote=$(echo $pkg_command | awk -F '|' '{print $4}')
command_require_root=$(echo $pkg_command | awk -F '|' '{print $5}')
echo -e "Vulnerable packages are:\n\n$(echo ${packages} | sed 's/^, //')\n"
if [[ ${command_require_root} == "root" ]]; then
if [[ ! $(id -u) -eq 0 ]]; then
echo -e "\nThis command requires root privileges.\n"
exit 0
fi
fi
for risk_parsed in ${risks[@]}; do
}
OLDIFS=${IFS}
IFS=";"
risk_parsed=(${risk_parsed})
IFS=${OLDIFS}
function runTool() {
echo "Security report date: $(date '+%d-%m-%Y, %X') (TZ: $(timedatectl status | grep "Time zone:" | awk '{print $3}'))"
# Package name
r_package_name="${risk_parsed[0]}"
echo -e "\nSynchronizing package databases with ${selected_manager}\n"
${selected_manager} ${command_refresh} || exit
echo -en "Analysing package ${i}/${#risks[*]} (${r_package_name})... \r"
if [[ ! $(which arch-audit | wc -l) -eq 1 ]]; then
echo -e "\nCouldn't find Arch Linux security utility (arch-audit) in \$PATH. Please make sure it's installed.\n"
else
# Package security issues detected
r_package_security_issues_count="${risk_parsed[2]}"
count=0
prs_count=0
IFS=$'\n'
# Package security issues overall level: Critical, High, Medium or Low
r_package_security_issues_level=$(echo "${risk_parsed[1]}" | sed 's/Critical/0/g; s/High/1/g; s/Medium/2/g; s/Low/3/g')
for i in $(arch-audit); do
package_name=$(echo "$i" | awk -F ' ' '{print $2}')
risk_level=$(echo "$i" | grep -oE "Low|Medium|High|Critical")
risks_count=$(echo "$i" | awk -F 'CVE' '{print NF-1}')
r_package_description=$(${SELECTED_MANAGER} "${command_pkginfo_local}" "${r_package_name}" | grep -i description | awk -F ": " '{print $2}')
risks[$count]="$package_name $risk_level $risks_count"
if [[ $(echo "${r_package_description}" | wc -m) -gt ${description_column_max_chars} ]]
then
r_package_description=$(printf "%s..." $(echo "${r_package_description}" | cut -c 1-${description_column_max_chars}))
fi
let count++
done
r_package_version_status=$(package_version_check "${r_package_name}")
echo -e "\nAnalyzing ${#risks[*]} vulnerable packages. This takes a while...\n"
risk_entries[$risks_parsed_count]=$(printf "%s|%s|%s|%s|%s\n" \
"${r_package_name}" \
"${r_package_security_issues_count}" \
"${r_package_security_issues_level}" \
"${r_package_version_status}" \
"${r_package_description}" \
)
let risks_parsed_count++
let i++
done
i=1
for risk_parsed in $(echo "${risks[*]}"); do
echo -en "Analysing package ${i}/${#risks[*]}... \r"
# Package in question
col1=$(echo "$risk_parsed" | awk -F ' ' '{print $1}')
# Security issues detected
col2=$(echo "$risk_parsed" | awk -F ' ' '{print $3}')
#Critical, High, Medium or Low risk
col3=$(echo "$risk_parsed" | awk -F ' ' '{print $2}' | sed 's/Critical/0/g; s/High/1/g; s/Medium/2/g; s/Low/3/g')
col5=$(${selected_manager} ${command_pkginfo_local} $col1 |grep -i description | awk -F ": " '{print $2}')
maxchars=35
if [[ $(echo $col5 | wc -m) -gt $maxchars ]]; then
col5=$(echo "$(echo $col5 | cut -c 1-$maxchars)...")
fi
versioncheck() {
# TODO: We can't really depend on parsing output strings since they vary between Arch package managers
parsedver() {
echo $1 | awk -F ' ' '{print $2}' | sed -r 's/[a-z]+.*//; s/[:_+-]/\./g; s/[^0-9]$//;'
}
# Expected output syntax: "^<string> <version number>$"
# TODO: We can't really depend on parsing output strings since they vary between Arch package managers
system_version=$(${selected_manager} ${command_pkginfo_local_short} $1)
repo_version=$(${selected_manager} ${command_pkginfo_remote} $1 | grep -E "^Version\s*:" | sed -r 's/.*(:\s*.*$)/\1/')
version_array_1=$(parsedver $system_version)
version_array_2=$(parsedver $repo_version)
#Count of version elements (0 18 2 1 contains 4 numbers, for example)
firstvernums=$(echo $version_array_1 | awk -F '.' '{print split($0, a)}')
lastvernums=$(echo $version_array_2 | awk -F '.' '{print split($0, a)}')
# Count of comparable version elements (maximum)
# We compare this much of elements, not more
if [[ $lastvernums -lt $firstvernums ]]; then
comparables=$lastvernums
else
comparables=$firstvernums
fi
# If all numbers are same, we don't analyze them more deeply.
if [[ $version_array_1 == $version_array_2 ]]; then
col4="${green}Package is updated"
else
s=1
while [ $s -le $comparables ]; do
check1=$(echo -e $version_array_1 | awk -v var=$s -F '.' '{print $var}')
check2=$(echo -e $version_array_2 | awk -v var=$s -F '.' '{print $var}')
if [[ $check2 -gt $check1 ]]; then
# Repo number is greater
col4="${yellow}Update available"
break
elif [[ $check2 -lt $check1 ]]; then
# System number is greater
col4="${reset}Newer package installed"
break
fi
let s++
done
fi
}
versioncheck $col1
risk_entries[$prs_count]=$(printf "%s|%s|%s|%s|%s\n" "$col1" "$col2" "$col3" "$col4" "$col5")
let prs_count++
let i++
done
echo -e "\e[1m"
printf "\n%-25s%-20s%-15s%-25s%s\n" "Package" "Security issues" "Risk level" "Version status" "Description"
echo -e "\e[0m"
sort_params=()
case ${sort_order} in
name)
sort_column="-k1"
;;
issues)
sort_column="-k2"
sort_params+=("-n")
;;
level)
sort_column="-k3"
;;
version)
sort_column="-k4"
;;
desc)
sort_column="-k5"
;;
#*)
# echo "Unknown sorting order selected. Exiting."
# exit 1
esac
if [[ ${sort_reverse} == 1 ]]; then
sort_params+=("-r")
fi
i=0
for line in $(echo "${risk_entries[*]}" | sort ${sort_params[*]} -t'|' ${sort_column}); do
if [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 0 ]]; then
alert_color="${red}"
importance="Critical"
elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 1 ]]; then
alert_color="${orange}"
importance="High"
elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 2 ]]; then
alert_color="${yellow}"
importance="Medium"
elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 3 ]]; then
alert_color="${green}"
importance="Low"
fi
sec_count=$(echo "$line" | awk -F '|' '{print $2}')
if [[ $sec_count -lt 5 ]]; then
secclr="${green}"
elif [[ $sec_count -ge 5 ]] && [[ $sec_count -lt 10 ]]; then
secclr="${yellow}"
elif [[ $sec_count -ge 10 ]] && [[ $sec_count -lt 20 ]]; then
secclr="${orange}"
elif [[ $sec_count -ge 20 ]]; then
secclr="${red}"
fi
secsum[$i]=$sec_count
echo "$line" | awk -F '|' -v clr1="${alert_color}" -v clr2="${secclr}" -v rs="${reset}" -v var="${importance}" '{printf "%-25s%s%-20s%s%-15s%-30s%s%s\n",$1,clr2,$2,clr1,var,$4,rs,$5}'
let i++
done
secsums_total=$(echo $(printf "%d+" ${secsum[@]})0 | bc)
echo -e "\nTotal:"
printf "%-25s%s\n\n" "$count" "$secsums_total"
printf "Check %s for more information.\n\n" "${provider}"
echo -e "\e[1m"
printf "\n%-25s%-20s%-15s%-25s%s\n" "Package" "Security issues" "Risk level" "Version status" "Description"
echo -e "\e[0m"
case "${SORT_ORDER}" in
name)
sort_column="-k1"
;;
issues)
sort_column="-k2"
sort_params+=("-n")
;;
level)
sort_column="-k3"
;;
version)
sort_column="-k4"
;;
desc)
sort_column="-k5"
;;
#*)
# echo "Unknown sorting order selected. Exiting."
# exit 1
esac
if [[ "${SORT_REVERSE}" -eq 1 ]]
then
sort_params+=("-r")
fi fi
i=0
IFS=$'\n'
for line in $(echo "${risk_entries[*]}" | sort ${sort_params[*]} -t'|' ${sort_column})
do
package_alert_importance_status=$(echo "${line}" | awk -F '|' '{print $3}')
case "${package_alert_importance_status}" in
0)
package_alert_msg_color="${red}"
package_alert_importance_output_status="Critical"
;;
1)
package_alert_msg_color="${orange}"
package_alert_importance_output_status="High"
;;
2)
package_alert_msg_color="${yellow}"
package_alert_importance_output_status="Medium"
;;
3)
package_alert_msg_color="${green}"
package_alert_importance_output_status="Low"
;;
esac
package_security_issues_count=$(echo "${line}" | awk -F '|' '{print $2}')
if [[ ${package_security_issues_count} -lt 5 ]]
then
security_msg_color="${green}"
elif [[ ${package_security_issues_count} -ge 5 ]] && [[ ${package_security_issues_count} -lt 10 ]]
then
security_msg_color="${yellow}"
elif [[ ${package_security_issues_count} -ge 10 ]] && [[ ${package_security_issues_count} -lt 20 ]]
then
security_msg_color="${orange}"
else
security_msg_color="${red}"
fi
security_issues_package_summary[$i]=${package_security_issues_count}
echo "${line}" | awk -F '|' \
-v clr1="${package_alert_msg_color}" \
-v clr2="${security_msg_color}" \
-v rs="${reset}" \
-v var="${package_alert_importance_output_status}" \
'{printf "%-25s%s%-20s%s%-15s%-30s%s%s\n",$1,clr2,$2,clr1,var,$4,rs,$5}'
let i++
done
security_issues_total_count=$(echo $(printf "%d+" "${security_issues_package_summary[@]}")0 | bc)
echo -e "\nTotal:"
printf "%-25s%s\n\n" "${package_count}" "${security_issues_total_count}"
printf "Check %s for more information.\n\n" "${NETWORK_HOST_ENDPOINT}"
fi
} }
inputParser
findMyPackageManager
internetTest
runTool
exec_tool

Loading…
Cancel
Save