Script para validar usuários locais nos servidores utilizando AdvPL

Hoje vou mostrar uma dica útil para quem quer monitorar invasões em servidores utilizando AdvPL.

Se você tem um Protheus instalado em uma rede com Windows, hoje vou mostrar uma dica interessante. Recentemente precisei criar uma função em AdvPL que ficasse rodando de tempos em tempos, verificando se algum usuário local foi criado em algum servidor. Esse processo de usuário local, geralmente é feito por invasores que querem sequestrar os dados das empresas.

Pois bem, como funciona essa rotina, basicamente através de um array (aServers), é executado um comando via PowerShell, e esse comando gera um arquivo csv com a lista de usuários locais no servidor (lembrando que o usuário que você estiver usando tem que ter privilégio para acessar esses servidores). O arquivo csv ficará dentro da pasta C:\Spool\zUsrSrv.

Ao rodar pela segunda vez, o arquivo csv será comparado com esse primeiro arquivo. Assim se houver diferenças, irá mostrar que foi criado algum usuário local novo, e você precisa ficar atento e conferir isso no seu servidor.

Para agendar, você pode agendar no Windows mesmo, pelo scheduler dele, e pode colocar para rodar a cada 4 horas +-. O comando, você pode colocar dentro de um bat, sendo basicamente apontando o exe, a conexão (nesse exemplo tcp), o ambiente (nesse exemplo ambiente_desenv) e qual é o programa (u_zUsrSrv). Abaixo o exemplo do comando completo:

C:\TOTVS\Producao\smartclient.exe -m -q -c=tcp -e=ambiente_desenv -p=u_zUsrSrv

Vale ressaltar que, confira a variável cPsw, coloque sua senha, sendo o ideal criar um parâmetro, ou alguma função para encapsular ela (como a clássica Embaralha).

Abaixo o código fonte desenvolvido:

//Bibliotecas
#Include "Totvs.ch"

Static nPosCod  := 1
Static nPosIP   := 2
Static nPosDNS  := 3
Static nPosDesc := 4
Static nPosDif  := 5
Static nPosOld  := 6
Static nPosNew  := 7

/*/{Protheus.doc} User Function zUsrSrv
Script para verificar usuarios locais nos servers
@type  Function
@author Atilio
@since 08/04/2020
@version version
@obs Essa funcao nao pode ser agendada no Scheduler por causa do PowerShell
    Ela deve ser executada via .bat (conteudo abaixo), agendada em uma maquina local (testada com Windows 10)

    C:\TOTVS\Producao\smartclient.exe -m -q -c=tcp -e=ambiente_desenv -p=u_zUsrSrv
/*/

User Function zUsrSrv()
    Local aArea := GetArea()
    Local cPsw	   := "Sua Senha"
    Private aServers := {}
    Private cDirSpool := "C:\spool\"

    //Se não estiver preparado, prepara o ambiente
	If Select("SX2") == 0  
   	 	RPCSetEnv("01", "01", "Admin", cPsw, "", "", {})
	EndIf

    //Se nao existir a pasta para o procedimento, cria ela
    If ! ExistDir(cDirSpool)
        MakeDir(cDirSpool)
    EndIf

    //Diretorio interno do spool
    cDirSpool += "zUsrSrv\"
    If ! ExistDir(cDirSpool)
        MakeDir(cDirSpool)
    EndIf

    //Monta os arrays
    fMontaSrv()

    //Faz as execucoes
    fExecCmd()

    //Faz as comparacoes
    fCompara()

    RestArea(aArea)
Return

Static Function fMontaSrv()
    //Adiciona os servidores e ips das maquinas que terao analise
    aAdd(aServers, {"111", "192.168.70.111", "srv01.dominio.org",    "DBAccess e License",         .F., "", ""})
    aAdd(aServers, {"112", "192.168.70.112", "srv02.dominio.org",    "Protheus e Protheus Data",   .F., "", ""})
    aAdd(aServers, {"113", "192.168.70.113", "srv03.dominio.org",    "Banco de Dados",             .F., "", ""})
Return

Static Function fExecCmd()
    Local nAtual := 1
    Local cCommand := ""
    Local cExecCmd := ""
    Local cDirTmp  := GetTempPath()
    Local cArqBat  := "servers_local_users.bat"
    Local cNewFile := ""
    Local cOldFile := ""
    
    //Montando a base do comando
    cCommand := 'powershell -Command "Get-WmiObject -ComputerName \"%zUsrSrv_DNS%\" -Class Win32_UserAccount -Filter '
    cCommand += '\"LocalAccount=' + "'True'\" + '" | Select PSComputername, Name, Status, Disabled, AccountType, Lockout, PasswordRequired, PasswordChangeable, SID '
    cCommand += '| Export-csv \"%zUsrSrv_FILE%\" -NoTypeInformation '// + CRLF + 'pause'

    //Percorre os servidores
    For nAtual := 1 To Len(aServers)
        cArqBat  := "servers_local_" + aServers[nAtual][nPosCod] + ".bat"

        //Exclui o arquivo novo
        cNewFile := cDirSpool + aServers[nAtual][nPosCod] + "_new.csv"
        FErase(cNewFile)

        //Cria .bat com comando
        cExecCmd := cCommand
        cExecCmd := StrTran(cExecCmd, "%zUsrSrv_DNS%",  aServers[nAtual][nPosIP])
        cExecCmd := StrTran(cExecCmd, "%zUsrSrv_FILE%", cNewFile)
        MemoWrite(cDirTmp + cArqBat, cExecCmd)

        //Executa o comando
        ShellExecute("Open", cArqBat, "", cDirTmp, 0 )
        Sleep(10000)

        //Pega o conteudo dele e coloca no array
        aServers[nAtual][nPosNew] := MemoRead(cNewFile)

        //Se o arquivo original nao existir, cria uma copia dele conforme o arquivo novo
        cOldFile := cDirSpool + aServers[nAtual][nPosCod] + "_old.csv"
        If ! File(cOldFile)
            MemoWrite(cOldFile, aServers[nAtual][nPosNew])
        EndIf
        aServers[nAtual][nPosOld] := MemoRead(cOldFile)

        //Compara os dois conteudos
        If aServers[nAtual][nPosNew] != aServers[nAtual][nPosOld]
            aServers[nAtual][nPosDif] := .T.
        EndIf
    Next
Return

Static Function fCompara()
    Local cCorpo := ""
    Local cVazio := ""
    Local nAtual := 0
    Local aConteudo := {}
    Local nPosUsr := 0
    Local cNomeUsr := ""
    Local cMensagem := ""
    Local cPara := u_zRetMail("zUsrSrv")

    //Percorrendo os servers
    For nAtual := 1 To Len(aServers)
        //Se houver diferencas
        If aServers[nAtual][nPosDif]
            cCorpo += "<h3>Servidor - " + aServers[nAtual][nPosIP] + " (" + aServers[nAtual][nPosDesc] + ")</h3>"

            //Percorre linhas do novo arquivo
            aConteudo := StrTokArr(aServers[nAtual][nPosNew], CRLF)
            cCorpo += "<ul>"
            For nPosUsr := 1 To Len(aConteudo)
                //Se nao for a linha de cabecalho
                If (aConteudo[nPosUsr] != '"PSComputerName","Name","Status","Disabled","AccountType","Lockout","PasswordRequired","PasswordChangeable","SID"')
                    cNomeUsr := aConteudo[nPosUsr]
                    cNomeUsr := SubStr(cNomeUsr, At(',', cNomeUsr) + 1, Len(cNomeUsr))
                    cNomeUsr := SubStr(cNomeUsr, 1,                      At(',', cNomeUsr) - 1)
                    cNomeUsr := StrTran(cNomeUsr, '"', '')

                    //Se o usuario estiver no antigo, saira normal, senao saira em negrito
                    If aConteudo[nPosUsr] $ aServers[nAtual][nPosOld]
                        cCorpo += "<li>" + cNomeUsr + "</li>"
                    Else
                        cCorpo += "<li><strong>" + cNomeUsr + "</strong></li>"
                    EndIf
                EndIf
            Next
            cCorpo += "</ul>"
            cCorpo += "<br>"
        EndIf

        //Se tiver vazio o conteudo
        If Empty(aServers[nAtual][nPosNew])
            cVazio += "<li>" + aServers[nAtual][nPosIP] + " (" + aServers[nAtual][nPosDNS] + " / " + aServers[nAtual][nPosDesc] + ")</li>"
        EndIf
    Next

    //Se tiver corpo do e-Mail
    If ! Empty(cCorpo) .Or. ! Empty(cVazio)
        cMensagem := "<p>Ola.</p><br>"
        If ! Empty(cCorpo)
            cMensagem += "<hr>"
            cMensagem += "<p>Os seguintes servidores tiveram alteracao recente nos usuarios locais, confira com urgencia!</p><br><br>"
            cMensagem += cCorpo + "<br>"
        EndIf

        //Se tiver arquivos vazios
        If ! Empty(cVazio)
            cMensagem += "<hr>"
            cMensagem += "<p>Nos seguintes servidores, nao foi possivel buscar os usuarios locais:</p><br>"
            cMensagem += "<ul>"
            cMensagem += cVazio
            cMensagem += "</ul>"
        EndIf

        //MemoWrite("C:\Users\daniel.atilio\Desktop\arquivo_tst.html", cMensagem)

        //Disparando o e-Mail
        u_EnviarEmail(	cPara,;                                    //Destinatário do e-Mail
                        "[dominio] - Usuarios Locais nos Servidores ",;   //Assunto
                        cMensagem,;                                    //Corpo do e-Mail
                        "",;                          //Arquivo anexo
                        ,;                                         //Grava log
                        ,;                                         //Deu certo disparo de e-Mail?
                        .F.)                                       //Novo tipo de envio?
    EndIf
Return

E você, gostou da solução divulgada hoje? Deixe nos comentários.

Bom pessoal, por hoje é só.

Abraços e até a próxima.

Dan Atilio (Daniel Atilio)
Especialista em Engenharia de Software pela FIB. Entusiasta de soluções Open Source. E blogueiro nas horas vagas.

Deixe uma resposta