Conheça brechas de segurança do Protheus e como preveni-las

No artigo de hoje vamos conhecer 2 dicas importantes para o ERP Protheus e como se prevenir.

Há um tempo atrás estava conversando com alguns amigos que são analistas internos na TOTVS Matriz, e perguntei se existia algum programa de Bug Hunt, pois eu conhecia 2 assuntos que poderiam ser considerados brechas em segurança.

 

Apesar de não ter algum programa assim na época (março desse ano), pelo menos na parte de customizações, passei pra eles os detalhes, então pode ser que os dois assuntos abaixo no futuro sejam tratados.

 

Então, a ideia desse artigo, é mostrar algumas coisas que podem causar impactos na base de vocês e como ajudar na prevenção. Vamos lá.

 

Assunto 1 – Senha disponível em ponto de entrada

Imagina o seguinte cenário, você é um cliente TOTVS, e que diversos analistas passam pela empresa, alguns de confiança e outros que você possui certo receio.

 

E se eu te falar que existe um ponto de entrada, que deixa a senha do usuário exposta e disponível? E se o analista em questão, cria uma forma de gravar as senhas como por exemplo usando MemoWrite e depois venda os acessos aos concorrentes ou à outras pessoas interessadas?

 

O que acontece é que existe um ponto de entrada chamado PswSize, que é acionado logo após fazer o login no sistema (até mesmo na tela nova em PO-UI). Mas nesse p.e., que serve para validar o tamanho da senha, ele vem também o conteúdo dela, então vejam por exemplo o print abaixo:

Exemplo da PswSize

 

Perceberam, que no PARAMIXB veio o usuário digitado e a senha digitada. Logo alguém malicioso (que consiga compilar o ponto de entrada na base), poderá ter a senha de diretores, de usuários chave, e de todos que usam o Protheus.

 

Como podemos prevenir? O primeiro passo é você ver se já existe esse ponto de entrada na sua base, se sim, verifica no fonte o que ele esta fazendo.

 

Se for o caso, você pode desativar o ponto de entrada conforme descrito nesse tutorial – https://terminaldeinformacao.com/2018/03/06/habilitar-desabilitar-funcoes-protheus/

 

Se precisarem para testar, abaixo o trecho do ponto de entrada demonstrado:

//Bibliotecas
#Include "TOTVS.ch"

/*/{Protheus.doc} User Function PswSize
Efetua a validação do usuário digitado
@type  Function
@author Atilio
@since 18/03/2023
@see https://tdn.totvs.com/pages/releaseview.action?pageId=6815189
/*/

User Function PswSize()
    Local aRet   := PARAMIXB

    If ! IsBlind()
        FWAlertInfo(aRet[2], "A senha é:")
    EndIf

Return aRet

Assunto 2 – Resetar senhas através de ExecAuto

Apesar de existir o processo via Configurador de alterar um usuário e resetar a senha. E se um analista criar um fonte com um ExecAuto para redefinir senhas, inclusive a senha do Administrador.

 

Mas como isso seria possível? Abaixo vamos falar sobre a lógica do funcionamento:

  1. A função deverá ser acionada no programa inicial (nesse caso u_zTestar)
  2. Será passado um usuário e senha no RPCSetEnv com privilégio de Admin
  3. A função zAtuSenha será acionada, passando o login do usuário e a nova senha
  4. Será carregado o ExecAuto da FWUserAccountData, resetando assim o usuário e senha

 

Então para o cenário acima, eu estou usando o usuário daniel e a senha tst123 (no RPCSetEnv), e eu resetei a senha do usuário Administrador para teste@2023, conforme fonte abaixo:

//Bibliotecas
#Include "TOTVS.ch"
#Include "FWMVCDef.ch"

/*/{Protheus.doc} User Function zAtuSenha
Função para resetar senha de usuários
@type  Function
@author Atilio
@since 02/06/2022
/*/

User Function zAtuSenha(cLoginUsr, cSenhaUsr)
	Local cAliasUsr := "MPUSR_USR"
    Local lDeuCerto := .F.

	//Abre a tabela de usuários e posiciona
	DbSelectArea(cAliasUsr)
	(cAliasUsr)->(DbSetOrder(2)) // USR_CODIGO
    
	If (cAliasUsr)->(MsSeek(cLoginUsr))

		//Se não usar o filtro ou tentar usar DbSetFilter no lugar de Set Filter To, ocasiona erro atualizando sempre o primeiro registro
		Set Filter To &("USR_CODIGO = '" + cLoginUsr + "'")

		//Ativa o modelo como alteração (CFGA510)
		oModel := FWLoadModel("FWUSERACCOUNTDATA")
		oModel:SetOperation(MODEL_OPERATION_UPDATE)
		oModel:Activate()

		//Define o campo de senha (nome do campo real é USR_SENHA, mas no model é os abaixo)
		oModel:SetValue("DATAUSER", "USR_PSW",    cSenhaUsr)
		oModel:SetValue("DATAUSER", "USR_PSWCMP", cSenhaUsr)

		//Se conseguir validar os dados e realizar o commit
		If oModel:VldData() .And. oModel:CommitData()
            lDeuCerto := .T.

		//Se não, houve erro, incrementa a mensagem
		Else
			//Busca o Erro do Modelo de Dados
			aErro := oModel:GetErrorMessage()

			//Monta o Texto que será mostrado na tela
			AutoGrLog("Id do formulário de origem:"  + ' [' + AllToChar(aErro[01]) + ']')
			AutoGrLog("Id do campo de origem: "      + ' [' + AllToChar(aErro[02]) + ']')
			AutoGrLog("Id do formulário de erro: "   + ' [' + AllToChar(aErro[03]) + ']')
			AutoGrLog("Id do campo de erro: "        + ' [' + AllToChar(aErro[04]) + ']')
			AutoGrLog("Id do erro: "                 + ' [' + AllToChar(aErro[05]) + ']')
			AutoGrLog("Mensagem do erro: "           + ' [' + AllToChar(aErro[06]) + ']')
			AutoGrLog("Mensagem da solução: "        + ' [' + AllToChar(aErro[07]) + ']')
			AutoGrLog("Valor atribuído: "            + ' [' + AllToChar(aErro[08]) + ']')
			AutoGrLog("Valor anterior: "             + ' [' + AllToChar(aErro[09]) + ']')

			//Mostra a mensagem de Erro
			MostraErro()
		EndIf

		//Desativa o modelo da memória
		oModel:DeActivate()
		FreeObj(oModel)

		DbSelectArea(cAliasUsr)
		Set Filter To
	EndIf
Return lDeuCerto


/*/{Protheus.doc} User Function zTestar
Função para testar execuções
@type  Function
@author Atilio
@since 20/02/2023
/*/

User Function zTestar()

    If Select("SX2") <= 0
		  RPCSetEnv("99", "01", "daniel", "tst123", "", "")
    EndIf

    u_zAtuSenha("Administrador", "teste@2023")

Return

E como nós poderíamos tratar essa situação? Bom, como a rotina é em MVC, podemos criar um ponto de entrada, que permite a alteração somente via SIGACFG. Mas esse p.e. tem uma particularidade, ele também é acionado na tela de login, e não tem o trecho do tudo ok quando é acionado via ExecAuto (FORMPOS / MODELPOS). Fique atento quando você for montá-lo. Abaixo segue um exemplo:

//Bibliotecas
#Include "Totvs.ch"

/*/{Protheus.doc} User Function FWUSERACCOUNTDATA
Ponto de Entrada do Cadastro de Usuários
@author Atilio
@since 04/11/2023
@version 1.0
@type function
@obs Codigo gerado automaticamente pelo Autumn Code Maker
     *-------------------------------------------------*
     Por se tratar de um p.e. em MVC, salve o nome do 
     arquivo diferente, por exemplo, FWUSERAC_pe.prw 
     *-----------------------------------------------*
     A documentacao de como fazer o p.e. esta disponivel em https://tdn.totvs.com/pages/releaseview.action?pageId=208345968 
@see http://autumncodemaker.com
/*/

User Function FWUSERACCOUNTDATA()
	Local aArea := FWGetArea()
	Local aParam := PARAMIXB 
	Local xRet := .T.
	Local oObj := Nil
	Local cIdPonto := ""
	Local cIdModel := ""
	
    //Se veio da tela de login, ignora
    If FWIsInCallStack("AUTHENTICATION") .Or. FWIsInCallStack("RPCSETENV")
        // ...

    Else
        //Se tiver parametros
        If aParam != Nil
            
            //Pega informacoes dos parametros
            oObj := aParam[1]
            cIdPonto := aParam[2]
            cIdModel := aParam[3]
            
            //Antes de gravar a tabela
            If cIdPonto == "FORMCOMMITTTSPRE" 

                //Se veio pelo configurador, pelo cadastro de usuários, permite prosseguir
                If FWIsInCallStack("CFGA510")
                    // ...

                //Se não veio, encerra a execução do programa
                Else
                    Final("Somente é possível atualizar via SIGACFG")
                EndIf
                
            EndIf
            
        EndIf
	EndIf

	FWRestArea(aArea)
Return xRet

Obs.: Não irei citar aqui as formas e ferramentas que usei para identificar, principalmente do assunto 2 que envolveu várias pesquisas, tentativas e erros.

Obs. 2: Amanhã irei subir um artigo de exemplo, do assunto 2, uma tela para resetar senha de usuários, então fiquem atentos as novidades aqui do site.

 

Bom pessoal por hoje é só.

Abraços e até a próxima.

Dan (Daniel Atilio)
Cristão de ramificação protestante. Especialista em Engenharia de Software pela FIB, graduado em Banco de Dados pela FATEC Bauru e técnico em informática pelo CTI da Unesp. Entusiasta de soluções Open Source e blogueiro nas horas vagas. Autor e mantenedor do portal Terminal de Informação.

Deixe uma resposta

Terminal de Informação