Выявить пользователей, которые вошли в систему как root


Это программа, написанная на Python 3.х и bash, чтобы проверить и найти пользователей, которые вошли в систему как root через sudo su, sudo -iи sudo bashи сообщить логин(Ы) и сколько раз они вошли в систему как root. Я интересно, если есть что-нибудь о моем код, который может быть изменен, чтобы сделать программу более эффективной. Одна вещь, я надеюсь, избавиться от потребность в bash-скрипт. Я использовал Баш, потому что это было легче найти конкретную информацию, я нуждался, каждый знал, пользователей в системе, и поместить его в отдельный файл быстро и легко. Честно, я не могу выяснить, как сделать это с Python.


root_login_search.py (основной файл/скрипт)

import re
from datetime import datetime, timedelta
from itertools import islice
import subprocess
import os

N = 7 # determines number of days ago, -1, that will be scanned through in auth.log (Current day: March 29, variable: 8, will look at the logs taken on March 22 through March 29)

def root_users():
    N_days_ago = datetime.now() - timedelta(days=N) # determines what days in the auth.log will be scanned, starting from 7 to 0 days prior to the current day
    # the two lines below changes date to unix/linux format(e.g. March  1 "or" March 20)
    date2 = N_days_ago.strftime("%b  %-d")
    date1 = N_days_ago.strftime("%b %d")
    tmp = open("tmp.txt", "w+")

    with open("/var/log/auth.log", "r") as txt: 
        for line in txt:
            if re.match("^%s.+" % date1, line) or re.match("^%s.+" % date2, line): # 1. takes all lines in /var/log/auth.log that were made N days ago
                if "Successful su for root by root" in line: # this will identify users who used "sudo su" # 2. then takes all lines that say "Successful su..."
                    lines_gen = islice(txt, 2) # 3. and takes the 2 lines below it...
                    tmp.writelines(lines_gen) # 4. then writes them into the tmp.txt file
                if re.match("^.+COMMAND=/bin/bash$", line): # this will identify users who use "sudo bash" and "sudo -i"
                    tmp.writelines(line)
    tmp.close()

    subprocess.call("./users.sh") # calls to and executes users.sh

    users = [] # a list/array of all known users on the system
    with open("users", "r") as txt: # places all users that are in the users file into the users array
        for u in txt:
            users.append(u)

    login = False # at this moment, no one has been detected as a user who has logged in as root
    root_users = []
    # checks the tmp.txt file to see if any known users are named within it
    with open("tmp.txt", "r") as txt:
        for line in txt:
            for word in re.findall(r"\w+", line):
                if word != "root": # this makes sure that if root is in a line, which it will always be, it won't add root to the root_users array
                    if word + "\n" in users:
                        root_users.append(word)
                        login = True # someone has been detected as someone who logged in as root
                        break # this break is placed here to prevent accidental miscount of times a user logged into the root account. it also prevents users who did not log in as root to be falsely tagged.

    print("On " + date1 + ":")
    for u in users: # goes through the users array where u = a single known user
        x = 0 # tallies the total amount of times a single users logged into the root account
        t = 0 # keeps track of where variable r is in the root_users array and once it become the length of root_users, it starts back at the top for loop, moving onto the next user in users
        for r in root_users: # goes through the root_users array where r = a single user
            t += 1
            if u == r + "\n":
                x += 1
            if t == len(root_users):
                if x >= 1:
                    print("    " + u + "     became root " + str(x) + " times.")

    if login == False:
        print("    No one became root")

    os.remove("users")
    os.remove("tmp.txt")

for i in range(N):
    N -= 1 # every time root_users() has gone through its course, one date earlier in auth.log will be scanned (May 7th will be scanned, next May 8th will be scanned, etc.)
    root_users()

users.sh (Sub файл/скрипт)

#!/bin/bash

USERS=`awk -F: '{ print $1 }' < /etc/passwd`

touch users
for u in $USERS; do
    echo $u >> users
done

Вот картинка из программы в действии: program in action



230
8
задан 2 апреля 2018 в 09:04 Источник Поделиться
Комментарии
2 ответа

Код предполагает, что у нас есть разрешения на запись в текущей рабочей директории. Вы, похоже, под влиянием заблуждения, что программа может быть запущена только из этого каталога. Как явный контрпример, когда я запускаю /bin/bashмой рабочий каталог не меняется в /bin. Так что это не безопасно предположить, что программа в $HOME напишу $HOME - ни то, что это безопасно, чтобы изменить файл называется users что вы там найдете.

Было бы лучше, чтобы создать временные файлы в каталоге, указанном в переменной окружения TMPDIR это де-факто стандартная переменная окружения, чтобы указать, где временные файлы должны быть созданы. Это обычно выбирается, чтобы быть где-то запись (очевидно), достаточно быстрый, и не обязательно постоянным (довольно часто, это файловая система tmpfs или иначе чистить ботинок, а в некоторых системах есть демон для удаления неиспользуемых файлов).


Bash скрипт странно - это может быть просто обычная оболочка один-лайнер:

#!/bin/sh
exec awk -F: '{ print $1 }' </etc/passwd >>users

Честно говоря, я думаю, что это лучше держать в питон, а не смешивать языки. К счастью, стандартная библиотека Python предоставляет удобства для разбора файла passwd для нас:

import pwd
users = [u.pw_name for u in pwd.getpwall()]


Я еще не полностью осмотрел кода Python, но такие вещи выделяются:

if login == False:

Большинство пишут, что просто как

if not login:

Кроме того, зачем нам перечитывать список пользователей для каждого дня, которые мы изучаем, а не только один раз, вне цикла?


Я не вижу никакой необходимости вообще читать полный список пользователей. Вместо этого, просто имена пользователей, которые мы видели в файле auth. Преимущество в том, что нам больше не нужно просматривать журналы на той же (или аналогичной, например, используя систему НИС), так как они были произведены, и пользователи, которые были удалены, или вообще никогда не существовал (явно подозрительно) может быть отображен.

Нам нужно только прочесть один раз файл, если мы ведем запись пользователей, которые имеют root-права на какие дни, как мы идем через него. Это как я бы сделал это:

import collections

from datetime import datetime, timedelta

N = 7 # how many days

def root_users():
today = datetime.now().date()
start_date = today - timedelta(days=N)
this_year = datetime.now().year
last_year = this_year - 1
days = collections.defaultdict(collections.Counter)

with open("/var/log/auth.log", "r") as txt:
for line in txt:
fields = line.split()
date_str = " ".join(fields[0:2]) + " "
try:
date = datetime.strptime(date_str + str(this_year), "%b %d %Y").date()
if date > today: raise ValueError
except ValueError:
date = datetime.strptime(date_str + str(last_year), "%b %d %Y").date()

if (date < start_date):
# too old for interest
continue
# "user : TTY=tty/1 ; PWD=/home/user ; USER=root ; COMMAND=/bin/su
if fields[4] == "sudo:":
user=fields[5]
if user != "root" and fields[-3] == "USER=root" and fields[-1] in ("COMMAND=/bin/bash", "COMMAND=/bin/sh", "COMMAND=/bin/su"):
days[date][user] += 1
# "Successful su for root by user"
if fields[4].startswith("su[") and fields[5] == "Successful" and fields[-3] == "root":
user=fields[-1]
if user != "root":
days[date][user] += 1

while start_date <= today:
print(start_date.strftime("On %b %d:"))
users = days[start_date]
if users:
for user,count in users.items():
print(" ", user, "became root", {1: 'once.', 2: 'twice.'}.get(count, str(count) + " times."))
else:
print(" No one became root")
start_date += timedelta(days=1)

root_users()

Вам может понадобиться для настройки вычислителей для вашей системы, формат даты и конкретное su и sudo журнал форматы - проверить, что комментарии согласен с тем, что вы видите в своем бревна. Вполне может быть, стоит извлечь вычислителей функции, которая возвращает либо имя пользователя или None для каждой строке журнала.

Одна вещь, вы могли бы рассмотреть, чтобы получить устойчивые линии журнала укладывать pam-модуль для создания именно журналов, которые вы хотите (т. е. использовать это в фильтр вместо того, чтобы делать, что здесь в мире питона). Это выходит за рамки данного ответа, но может быть стоит исследовать.


Наконец, нам может понадобиться взглянуть на старые auth.log файлы (если logrotate была на работе, к примеру). Рассмотрите возможность использования fileinput.FileInput чтобы перебрать некоторые/все лог-файлы (вы могли бы рассматривать только те, чье время последнего изменения по крайней мере start_date). Если старые файлы сжимаются, вы хотите, чтобы указать соответствующее openhook аргумент при создании FileInput объект.


Ответы на некоторые конкретные вопросы о моей версии:

Мы никогда не рассчитывать "Successful su for root by root"потому что это не переход (мы уже рассчитывать пользователи, которые sudo suпотому что su один из совпавших COMMAND струны для sudo журнал линия). Мы только рассчитывать su когда она запущена не из-под суперпользователя.

days = collections.defaultdict(collections.Counter) это defaultdict какие карты объектов (в нашем случае времени) на счетчики. Когда мы смотрим на сегодняшний день, мы получаем в ответ Counter объект на эту дату, новый создается при необходимости. В Counter это тоже своего рода словарь, в этот раз сопоставление имен пользователей, сколько раз они были замечены в лог-файл за эту дату.

Этот блок кода, чтобы найти правильный год на дату:

        try:
date = datetime.strptime(date_str + str(this_year), "%b %d %Y").date()
if date > today: raise ValueError
except ValueError:
date = datetime.strptime(date_str + str(last_year), "%b %d %Y").date()

Мы сначала угадать, что дата в этом году. Если дата 29 февраля и этот год не високосный, datetime.strptime() будут бросать ValueError; мы можем уловить, и пересмотреть наше предположение, чтобы быть в прошлом году. Если дата в будущем, мы также хотим пересмотреть наше предположение в прошлом году - это немного рубить, но самый короткий путь для достижения этого-повышение ValueError себя, поэтому мы не имеем дополнительный путь через логику.

Когда мы перебираем пользователей на определенный день, мы читаем с Counter. Помните, что это словарь, который сопоставляет имя пользователя для подсчета вхождений, поэтому ключом является имя, а значением является число:

            for user,count in users.items():

И когда мы печатаем результаты, мы могли бы использовать + чтобы объединить строки в одну большую строку для печати, но, передав их как отдельные аргументы, мы экономим память и время обработки. (Я тогда украсть некоторые выгоды путем построения словаря один раз/два раза/n_times просто выбрать один из его элементов - что может быть лучше, перешел на функцию, на самом деле).

3
ответ дан 3 апреля 2018 в 08:04 Источник Поделиться

Список пользователей

Я думаю, что вы должны полностью обойти скрипт и использовать вместо питона. Нам повезло, второй пример в формате CSV модуль документации об анализе passwd файл.

Так что вы могли бы использовать что-то вроде:

import csv
with open('passwd', newline='') as f:
reader = csv.reader(f, delimiter=':', quoting=csv.QUOTE_NONE)
users = [row[0] for row in reader]

Таким образом, Вы непосредственно иметь список пользователей, не прибегая к другой файл или скрипт.

Временные файлы

Использовать tempfile модуль для обработки временных файлов неаккуратно; что позволяет убедиться, что они удалены. Но в вашем случае, вы, вероятно, даже не нужно их и следует хранить списки пользователя и так в простые списки Python вместо внешних файлов.

Количество пользователей

Подсчет пользователей может быть намного упрощена:

# Suppose we have a list of known users:
users = ["abe", "barb", "charles"]
# And a list of users that became root:
root_users = ["abe", "abe", "barb", "abe", "barb"]
# The following then prints how many times each user became root
for u in users:
count = root_users.count(u)
if count:
print(u, count)

Я думаю, что этот фрагмент должен дать вам идею.

Общие вещи

Вы должны запустить Линтер (т. е. flake8 или так), что бы дать вам советы, чтобы помочь вам сделать ваш код более читабельным. Придирки пример: комментарии должны быть размещены на отдельной строке, когда они достаточно долго.

Глобальные переменные, как правило, отговаривают: вы должны переопределить root_users() взять N в качестве параметра вместо. Пока мы здесь, ты сможешь прочитать passwd файл только один раз перед вызовом root_users()и передайте его пользователи такие же !

Есть много мелких улучшений, которые позволят уменьшить сложность вашего сценария. Я, наверное, не их всех накрыло, но я надеюсь, чтобы вы начали. Не стесняйтесь обратиться за разъяснениями, ура !

4
ответ дан 4 апреля 2018 в 08:04 Источник Поделиться