Skip to content

Commit 96c2c14

Browse files
ejohnstownphilljj
authored andcommitted
internal: refuse auth replies before a request
The client filter now refuses every userauth message, the banner included, until CONNECT_CLIENT_USERAUTH_SENT, so a SUCCESS sent in place of the SERVICE_ACCEPT no longer completes wolfSSH_connect(). The first request goes out right after the service accept, RFC 4252 section 5.4. - regress: test each userauth message in the pre-request states - regress: drive an early SUCCESS, FAILURE and PK_OK through DoReceive - regress: a banner bundled behind the service accept still gets through wolfSSH_connect() Issue: #1285
1 parent 39983c1 commit 96c2c14

2 files changed

Lines changed: 151 additions & 2 deletions

File tree

‎src/internal.c‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -984,8 +984,10 @@ INLINE static int IsMessageAllowedClient(WOLFSSH *ssh, byte msg)
984984
return 0;
985985
}
986986
else if (MSGIDLIMIT_AUTH(msg)) {
987-
/* Do not accept any userauth messages until we've asked for auth. */
988-
if (ssh->connectState < CONNECT_CLIENT_USERAUTH_REQUEST_SENT) {
987+
/* No userauth message, the banner included, before our first
988+
* request. wolfSSH_connect() sends it before reading past the
989+
* service accept. */
990+
if (ssh->connectState < CONNECT_CLIENT_USERAUTH_SENT) {
989991
WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by %s %s",
990992
msg, "client", "before sending userauth request");
991993
ssh->error = WS_MSGID_NOT_ALLOWED_E;

‎tests/regress.c‎

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3725,6 +3725,145 @@ static void TestClientServiceAcceptBlockedDuringKeying(WOLFSSH* ssh)
37253725
}
37263726

37273727

3728+
/* Until the first userauth request goes out, no userauth message may
3729+
* arrive, the banner included. */
3730+
static void TestClientUserauthReplyBeforeRequest(WOLFSSH* ssh)
3731+
{
3732+
static const byte msgs[] = {
3733+
MSGID_USERAUTH_FAILURE,
3734+
MSGID_USERAUTH_SUCCESS,
3735+
MSGID_USERAUTH_BANNER,
3736+
MSGID_USERAUTH_PK_OK, /* also INFO_REQUEST and PW_CHRQ */
3737+
};
3738+
static const byte states[] = {
3739+
CONNECT_KEYED,
3740+
CONNECT_CLIENT_USERAUTH_REQUEST_SENT,
3741+
CONNECT_SERVER_USERAUTH_REQUEST_DONE,
3742+
};
3743+
word32 i, j;
3744+
int allowed;
3745+
3746+
for (i = 0; i < sizeof(states)/sizeof(*states); i++) {
3747+
for (j = 0; j < sizeof(msgs)/sizeof(*msgs); j++) {
3748+
ResetSession(ssh);
3749+
ssh->connectState = states[i];
3750+
allowed = wolfSSH_TestIsMessageAllowed(ssh, msgs[j],
3751+
WS_MSG_RECV);
3752+
AssertFalse(allowed);
3753+
AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E);
3754+
}
3755+
}
3756+
3757+
/* The service accept still answers the service request. */
3758+
ResetSession(ssh);
3759+
ssh->connectState = CONNECT_CLIENT_USERAUTH_REQUEST_SENT;
3760+
allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_SERVICE_ACCEPT,
3761+
WS_MSG_RECV);
3762+
AssertTrue(allowed);
3763+
3764+
/* Once the first userauth request is out, all of them are allowed. */
3765+
for (j = 0; j < sizeof(msgs)/sizeof(*msgs); j++) {
3766+
ResetSession(ssh);
3767+
ssh->connectState = CONNECT_CLIENT_USERAUTH_SENT;
3768+
allowed = wolfSSH_TestIsMessageAllowed(ssh, msgs[j],
3769+
WS_MSG_RECV);
3770+
AssertTrue(allowed);
3771+
}
3772+
}
3773+
3774+
3775+
/* A userauth reply to the service request draws a disconnect and leaves
3776+
* serverState alone. */
3777+
static void TestClientUnsolicitedUserauthReplyRejected(byte msgId)
3778+
{
3779+
WOLFSSH_CTX* ctx;
3780+
WOLFSSH* ssh;
3781+
MemIo io;
3782+
byte pkt[32];
3783+
byte out[256];
3784+
word32 pktSz;
3785+
3786+
pktSz = BuildPacket(msgId, pkt, sizeof(pkt));
3787+
3788+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
3789+
AssertNotNull(ctx);
3790+
wolfSSH_SetIORecv(ctx, MemRecv);
3791+
wolfSSH_SetIOSend(ctx, MemSend);
3792+
3793+
ssh = wolfSSH_new(ctx);
3794+
AssertNotNull(ssh);
3795+
3796+
MemIoInit(&io, pkt, pktSz, out, sizeof(out));
3797+
wolfSSH_SetIOReadCtx(ssh, &io);
3798+
wolfSSH_SetIOWriteCtx(ssh, &io);
3799+
3800+
ssh->connectState = CONNECT_CLIENT_USERAUTH_REQUEST_SENT;
3801+
ssh->serverState = SERVER_KEXINIT_DONE;
3802+
3803+
AssertIntEQ(wolfSSH_TestDoReceive(ssh), WS_FATAL_ERROR);
3804+
AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E);
3805+
AssertIntEQ(ssh->serverState, SERVER_KEXINIT_DONE);
3806+
AssertTrue(io.outSz > 0);
3807+
AssertIntEQ(ParseMsgId(io.out, io.outSz), MSGID_DISCONNECT);
3808+
AssertTrue(ssh->disconnected);
3809+
3810+
wolfSSH_free(ssh);
3811+
wolfSSH_CTX_free(ctx);
3812+
}
3813+
3814+
3815+
/* Unused by a "none" request, which still needs a callback. */
3816+
static int NoneUserAuthCb(byte authType, WS_UserAuthData* authData,
3817+
void* ctx)
3818+
{
3819+
(void)authType;
3820+
(void)authData;
3821+
(void)ctx;
3822+
return WOLFSSH_USERAUTH_INVALID_AUTHTYPE;
3823+
}
3824+
3825+
/* A banner bundled behind the service accept, as wolfSSH's server sends
3826+
* it, is read after wolfSSH_connect() sends the first request. */
3827+
static void TestClientBannerBehindServiceAccept(void)
3828+
{
3829+
ChannelOpenHarness harness;
3830+
byte in[128];
3831+
byte payload[64];
3832+
word32 inSz;
3833+
word32 payloadSz;
3834+
3835+
payloadSz = AppendString(payload, sizeof(payload), 0, "ssh-userauth");
3836+
inSz = WrapPacket(MSGID_SERVICE_ACCEPT, payload, payloadSz,
3837+
in, sizeof(in));
3838+
payloadSz = AppendString(payload, sizeof(payload), 0, "banner");
3839+
payloadSz = AppendString(payload, sizeof(payload), payloadSz, "");
3840+
inSz += WrapPacket(MSGID_USERAUTH_BANNER, payload, payloadSz,
3841+
in + inSz, sizeof(in) - inSz);
3842+
3843+
InitChannelOpenHarnessClient(&harness, in, inSz);
3844+
wolfSSH_SetUserAuth(harness.ctx, NoneUserAuthCb);
3845+
AssertIntEQ(wolfSSH_SetUsername(harness.ssh, "user"), WS_SUCCESS);
3846+
harness.ssh->connectState = CONNECT_CLIENT_USERAUTH_REQUEST_SENT;
3847+
harness.ssh->serverState = SERVER_KEXINIT_DONE;
3848+
3849+
/* Both packets read, then WANT_READ for the auth answer. */
3850+
AssertIntEQ(wolfSSH_connect(harness.ssh), WS_FATAL_ERROR);
3851+
AssertIntEQ(wolfSSH_get_error(harness.ssh), WS_WANT_READ);
3852+
AssertIntEQ(harness.io.inOff, inSz);
3853+
AssertIntEQ(harness.ssh->connectState, CONNECT_CLIENT_USERAUTH_SENT);
3854+
AssertIntEQ(harness.ssh->serverState, SERVER_USERAUTH_REQUEST_DONE);
3855+
AssertFalse(harness.ssh->disconnected);
3856+
/* Only the userauth request went out. */
3857+
AssertTrue(harness.io.outSz > 0);
3858+
AssertIntEQ(ParseMsgId(harness.io.out, harness.io.outSz),
3859+
MSGID_USERAUTH_REQUEST);
3860+
AssertIntEQ(NextPacketOffset(harness.io.out, harness.io.outSz),
3861+
harness.io.outSz);
3862+
3863+
FreeChannelOpenHarness(&harness);
3864+
}
3865+
3866+
37283867
/* Drive the whole receive path with a CHANNEL_OPEN sent from a pre-auth
37293868
* connectState: no channel created, and the only thing sent back is the
37303869
* disconnect RFC 4252 section 6 asks for. The connectState gate does the
@@ -18635,6 +18774,14 @@ int main(int argc, char** argv)
1863518774
TestChannelAllowedAfterAuth(ssh);
1863618775
TestClientOnlyKexMsgsBlocked(ssh);
1863718776
TestClientServiceAcceptBlockedDuringKeying(ssh);
18777+
TestClientUserauthReplyBeforeRequest(ssh);
18778+
TestClientUnsolicitedUserauthReplyRejected(MSGID_USERAUTH_SUCCESS);
18779+
TestClientUnsolicitedUserauthReplyRejected(MSGID_USERAUTH_FAILURE);
18780+
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
18781+
/* Id 60 is only a known id as INFO_REQUEST. */
18782+
TestClientUnsolicitedUserauthReplyRejected(MSGID_USERAUTH_PK_OK);
18783+
#endif
18784+
TestClientBannerBehindServiceAccept();
1863818785
TestChannelOpenRejectedBeforeKex(CONNECT_CLIENT_KEXINIT_SENT);
1863918786
TestChannelOpenRejectedBeforeKex(CONNECT_CLIENT_KEXDH_INIT_SENT);
1864018787
TestSessionOnClientSendsOpenFail();

0 commit comments

Comments
 (0)