Skip to content

Commit

Permalink
Merge pull request #536 from bareos/dev/franku/master/cram-md5
Browse files Browse the repository at this point in the history
cram-md5: do not accept challenge if own hostname is used
  • Loading branch information
franku committed Jul 1, 2020
2 parents 94a0525 + a105df3 commit fd28191
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 31 deletions.
20 changes: 9 additions & 11 deletions core/src/dird/authenticate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Copyright (C) 2001-2008 Free Software Foundation Europe e.V.
Copyright (C) 2011-2012 Planets Communications B.V.
Copyright (C) 2013-2018 Bareos GmbH & Co. KG
Copyright (C) 2013-2020 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
Expand Down Expand Up @@ -46,6 +46,8 @@
#include "lib/parse_conf.h"
#include "lib/util.h"

#include <array>

namespace directordaemon {

static const int debuglevel = 50;
Expand Down Expand Up @@ -166,16 +168,12 @@ bool AuthenticateWithFileDaemon(JobControlRecord* jcr)
client->password_, client);

if (!auth_success) {
Dmsg2(debuglevel, "Unable to authenticate with File daemon at \"%s:%d\"\n",
fd->host(), fd->port());
Jmsg(jcr, M_FATAL, 0,
_("Unable to authenticate with File daemon at \"%s:%d\". Possible "
"causes:\n"
"Passwords or names not the same or\n"
"TLS negotiation failed or\n"
"Maximum Concurrent Jobs exceeded on the FD or\n"
"FD networking messed up (restart daemon).\n"),
fd->host(), fd->port());
std::array<char, 1024> msg;
const char* fmt =
_("Unable to authenticate with File daemon at \"%s:%d\"\n");
snprintf(msg.data(), msg.size(), fmt, fd->host(), fd->port());
Dmsg0(debuglevel, msg.data());
Jmsg(jcr, M_FATAL, 0, msg.data());
return false;
}

Expand Down
57 changes: 51 additions & 6 deletions core/src/lib/bsock.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Copyright (C) 2007-2011 Free Software Foundation Europe e.V.
Copyright (C) 2011-2012 Planets Communications B.V.
Copyright (C) 2013-2018 Bareos GmbH & Co. KG
Copyright (C) 2013-2020 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
Expand Down Expand Up @@ -385,12 +385,17 @@ bool BareosSocket::TwoWayAuthenticate(JobControlRecord* jcr,
bool auth_success = false;

if (jcr && JobCanceled(jcr)) {
Dmsg0(debuglevel, "Failed, because job is canceled.\n");
const char* fmt =
_("TwoWayAuthenticate failed, because job was canceled.\n");
Jmsg(jcr, M_FATAL, 0, fmt);
Dmsg0(debuglevel, fmt);
} else if (password.encoding != p_encoding_md5) {
Jmsg(jcr, M_FATAL, 0,
const char* fmt =
_("Password encoding is not MD5. You are probably restoring a NDMP "
"Backup "
"with a restore job not configured for NDMP protocol.\n"));
"with a restore job not configured for NDMP protocol.\n");
Jmsg(jcr, M_FATAL, 0, fmt);
Dmsg0(debuglevel, fmt);
} else {
TlsPolicy local_tls_policy = tls_resource->GetPolicy();
CramMd5Handshake cram_md5_handshake(this, password.value, local_tls_policy,
Expand All @@ -400,18 +405,58 @@ bool BareosSocket::TwoWayAuthenticate(JobControlRecord* jcr,

if (ConnectionReceivedTerminateSignal()) {
if (tid) { StopBsockTimer(tid); }
const char* fmt =
_("TwoWayAuthenticate failed, because connection was reset by "
"destination peer.\n");
Jmsg(jcr, M_FATAL, 0, fmt);
Dmsg0(debuglevel, fmt);
return false;
}

auth_success = cram_md5_handshake.DoHandshake(initiated_by_remote);

if (!auth_success) {
Jmsg(jcr, M_FATAL, 0, _("Authorization key rejected %s.\n"), identity);
char ipaddr_str[MAXHOSTNAMELEN]{};
SockaddrToAscii(&(client_addr), ipaddr_str, sizeof(ipaddr_str));

switch (cram_md5_handshake.result) {
case CramMd5Handshake::HandshakeResult::REPLAY_ATTACK: {
const char* fmt =
"Warning! Attack detected: "
"I will not answer to my own challenge. "
"Please check integrity of the host at IP address: %s\n";
Jmsg(jcr, M_FATAL, 0, fmt, ipaddr_str);
Dmsg1(debuglevel, fmt, ipaddr_str);
break;
}
case CramMd5Handshake::HandshakeResult::NETWORK_ERROR:
Jmsg(jcr, M_FATAL, 0, _("Network error during CRAM MD5 with %s\n"),
ipaddr_str);
break;
case CramMd5Handshake::HandshakeResult::WRONG_HASH:
Jmsg(jcr, M_FATAL, 0, _("Authorization key rejected by %s.\n"),
ipaddr_str);
break;
case CramMd5Handshake::HandshakeResult::FORMAT_MISMATCH:
Jmsg(jcr, M_FATAL, 0,
_("Wrong format of the CRAM challenge with %s.\n"), ipaddr_str);
default:
break;
}
fsend(_("1999 Authorization failed.\n"));
Bmicrosleep(sleep_time_after_authentication_error, 0);
} else if (jcr && JobCanceled(jcr)) {
Dmsg0(debuglevel, "Failed, because job is canceled.\n");
const char* fmt =
_("TwoWayAuthenticate failed, because job was canceled.\n");
Jmsg(jcr, M_FATAL, 0, fmt);
Dmsg0(debuglevel, fmt);
auth_success = false;
} else if (!DoTlsHandshake(cram_md5_handshake.RemoteTlsPolicy(),
tls_resource, initiated_by_remote, identity,
password.value, jcr)) {
const char* fmt = _("Tls handshake failed.\n");
Jmsg(jcr, M_FATAL, 0, fmt);
Dmsg0(debuglevel, fmt);
auth_success = false;
}
if (tid) { StopBsockTimer(tid); }
Expand Down
66 changes: 60 additions & 6 deletions core/src/lib/cram_md5.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
BAREOS® - Backup Archiving REcovery Open Sourced
Copyright (C) 2001-2011 Free Software Foundation Europe e.V.
Copyright (C) 2013-2018 Bareos GmbH & Co. KG
Copyright (C) 2013-2020 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
Expand All @@ -29,6 +29,7 @@
#include "include/bareos.h"
#include "lib/cram_md5.h"
#include "lib/bsock.h"
#include "lib/util.h"


CramMd5Handshake::CramMd5Handshake(BareosSocket* bs,
Expand All @@ -39,10 +40,34 @@ CramMd5Handshake::CramMd5Handshake(BareosSocket* bs,
, password_(password)
, local_tls_policy_(local_tls_policy)
, own_qualified_name_(own_qualified_name)
, own_qualified_name_bashed_spaces_(own_qualified_name_)
{
return;
BashSpaces(own_qualified_name_bashed_spaces_);
}

CramMd5Handshake::ComparisonResult
CramMd5Handshake::CompareChallengeWithOwnQualifiedName(
const char* challenge) const
{
uint32_t a, b;
char buffer[MAXHOSTNAMELEN]{"?"}; // at least one character

bool scan_success = sscanf(challenge, "<%u.%u@%s", &a, &b, buffer) == 3;

// string contains the closing ">" of the challenge
std::string challenge_qualified_name(buffer, strlen(buffer) - 1);

Dmsg1(debuglevel_, "my_name: <%s> - challenge_name: <%s>\n",
own_qualified_name_bashed_spaces_.c_str(),
challenge_qualified_name.c_str());

if (!scan_success) { return ComparisonResult::FAILURE; }

// check if the name in the challenge matches the daemons name
return own_qualified_name_bashed_spaces_ == challenge_qualified_name
? ComparisonResult::IS_SAME
: ComparisonResult::IS_DIFFERENT;
}

/* Authorize other end
* Codes that tls_local_need and tls_remote_need can take:
Expand All @@ -60,12 +85,10 @@ bool CramMd5Handshake::CramMd5Challenge()

InitRandom();

host.check_size(MAXHOSTNAMELEN);
if (!gethostname(host.c_str(), MAXHOSTNAMELEN)) { PmStrcpy(host, my_name); }

/* Send challenge -- no hashing yet */
Mmsg(chal, "<%u.%u@%s>", (uint32_t)random(), (uint32_t)time(NULL),
host.c_str());
own_qualified_name_bashed_spaces_.c_str());

if (bs_->IsBnetDumpEnabled()) {
Dmsg2(debuglevel_, "send: auth cram-md5 %s ssl=%d qualified-name=%s\n",
Expand All @@ -75,6 +98,7 @@ bool CramMd5Handshake::CramMd5Challenge()
local_tls_policy_, own_qualified_name_.c_str())) {
Dmsg1(debuglevel_, "Bnet send challenge comm error. ERR=%s\n",
bs_->bstrerror());
result = HandshakeResult::NETWORK_ERROR;
return false;
}
} else { // network dump disabled
Expand All @@ -85,6 +109,7 @@ bool CramMd5Handshake::CramMd5Challenge()
local_tls_policy_)) {
Dmsg1(debuglevel_, "Bnet send challenge comm error. ERR=%s\n",
bs_->bstrerror());
result = HandshakeResult::NETWORK_ERROR;
return false;
}
}
Expand All @@ -94,6 +119,7 @@ bool CramMd5Handshake::CramMd5Challenge()
Dmsg1(debuglevel_, "Bnet receive challenge response comm error. ERR=%s\n",
bs_->bstrerror());
Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
result = HandshakeResult::NETWORK_ERROR;
return false;
}

Expand All @@ -102,6 +128,7 @@ bool CramMd5Handshake::CramMd5Challenge()
hmac_md5((uint8_t*)chal.c_str(), strlen(chal.c_str()), (uint8_t*)password_,
strlen(password_), hmac);
BinToBase64(host.c_str(), MAXHOSTNAMELEN, (char*)hmac, 16, compatible_);

bool ok = bstrcmp(bs_->msg, host.c_str());
if (ok) {
Dmsg1(debuglevel_, "Authenticate OK %s\n", host.c_str());
Expand All @@ -114,8 +141,10 @@ bool CramMd5Handshake::CramMd5Challenge()
}
}
if (ok) {
result = HandshakeResult::SUCCESS;
bs_->fsend("1000 OK auth\n");
} else {
result = HandshakeResult::WRONG_HASH;
bs_->fsend(_("1999 Authorization failed.\n"));
Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
}
Expand All @@ -130,6 +159,7 @@ bool CramMd5Handshake::CramMd5Response()
compatible_ = false;
if (bs_->recv() <= 0) {
Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
result = HandshakeResult::NETWORK_ERROR;
return false;
}

Expand All @@ -148,6 +178,7 @@ bool CramMd5Handshake::CramMd5Response()
Dmsg1(debuglevel_, "Cannot scan challenge: %s", bs_->msg);
bs_->fsend(_("1999 Authorization failed.\n"));
Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
result = HandshakeResult::FORMAT_MISMATCH;
return false;
}
}
Expand All @@ -162,16 +193,34 @@ bool CramMd5Handshake::CramMd5Response()
Dmsg1(debuglevel_, "Cannot scan challenge: %s", bs_->msg);
bs_->fsend(_("1999 Authorization failed.\n"));
Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
result = HandshakeResult::FORMAT_MISMATCH;
return false;
}
}
}

auto comparison_result = CompareChallengeWithOwnQualifiedName(chal.c_str());

if (comparison_result == ComparisonResult::IS_SAME) {
std::string c(chal.c_str());
// same sd-sd connection should be possible i.e. for copy jobs
if (c.rfind("R_STORAGE") == std::string::npos) {
result = HandshakeResult::REPLAY_ATTACK;
return false;
}
}

if (comparison_result == ComparisonResult::FAILURE) {
result = HandshakeResult::FORMAT_MISMATCH;
return false;
}

hmac_md5((uint8_t*)chal.c_str(), strlen(chal.c_str()), (uint8_t*)password_,
strlen(password_), hmac);
bs_->message_length =
BinToBase64(bs_->msg, 50, (char*)hmac, 16, compatible_) + 1;
if (!bs_->send()) {
result = HandshakeResult::NETWORK_ERROR;
Dmsg1(debuglevel_, "Send challenge failed. ERR=%s\n", bs_->bstrerror());
return false;
}
Expand All @@ -180,9 +229,14 @@ bool CramMd5Handshake::CramMd5Response()
Dmsg1(debuglevel_, "Receive challenge response failed. ERR=%s\n",
bs_->bstrerror());
Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
result = HandshakeResult::NETWORK_ERROR;
return false;
}
if (bstrcmp(bs_->msg, "1000 OK auth\n")) { return true; }
if (bstrcmp(bs_->msg, "1000 OK auth\n")) {
result = HandshakeResult::SUCCESS;
return true;
}
result = HandshakeResult::WRONG_HASH;
Dmsg1(debuglevel_, "Received bad response: %s\n", bs_->msg);
Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
return false;
Expand Down
25 changes: 24 additions & 1 deletion core/src/lib/cram_md5.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
BAREOS® - Backup Archiving REcovery Open Sourced
Copyright (C) 2018-2018 Bareos GmbH & Co. KG
Copyright (C) 2018-2020 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
Expand Down Expand Up @@ -35,6 +35,18 @@ class CramMd5Handshake {
TlsPolicy RemoteTlsPolicy() const { return remote_tls_policy_; }
std::string destination_qualified_name_;

enum class HandshakeResult
{
NOT_INITIALIZED,
SUCCESS,
FORMAT_MISMATCH,
NETWORK_ERROR,
WRONG_HASH,
REPLAY_ATTACK
};

mutable HandshakeResult result{HandshakeResult::NOT_INITIALIZED};

private:
static constexpr int debuglevel_ = 50;
bool compatible_ = true;
Expand All @@ -43,9 +55,20 @@ class CramMd5Handshake {
TlsPolicy local_tls_policy_ = kBnetTlsUnknown;
TlsPolicy remote_tls_policy_ = kBnetTlsUnknown;
const std::string own_qualified_name_;
std::string own_qualified_name_bashed_spaces_;
bool CramMd5Challenge();
bool CramMd5Response();
void InitRandom() const;

enum class ComparisonResult
{
FAILURE,
IS_SAME,
IS_DIFFERENT
};

ComparisonResult CompareChallengeWithOwnQualifiedName(
const char* challenge) const;
};

void hmac_md5(uint8_t* text,
Expand Down
11 changes: 6 additions & 5 deletions core/src/lib/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "include/version_numbers.h"

#include <algorithm>
#include <string>

/*
* Various BAREOS Utility subroutines
Expand Down Expand Up @@ -136,10 +137,11 @@ void BashSpaces(char* str)
}
}

/*
* Convert spaces to non-space character.
* This makes scanf of fields containing spaces easier.
*/
void BashSpaces(std::string& str)
{
std::replace(str.begin(), str.end(), ' ', static_cast<char>(0x1));
}

void BashSpaces(PoolMem& pm)
{
char* str = pm.c_str();
Expand All @@ -149,7 +151,6 @@ void BashSpaces(PoolMem& pm)
}
}


/*
* Convert non-space characters (0x1) back into spaces
*/
Expand Down
1 change: 1 addition & 0 deletions core/src/lib/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ void EscapeString(PoolMem& snew, const char* old, int len);
bool IsBufZero(char* buf, int len);
void lcase(char* str);
void BashSpaces(char* str);
void BashSpaces(std::string& str);
void BashSpaces(PoolMem& pm);
void UnbashSpaces(char* str);
void UnbashSpaces(PoolMem& pm);
Expand Down
Loading

0 comments on commit fd28191

Please sign in to comment.