Skip to content

Commit 9b8d721

Browse files
committed
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 6ed8e12 commit 9b8d721

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
@@ -982,8 +982,10 @@ INLINE static int IsMessageAllowedClient(WOLFSSH *ssh, byte msg)
982982
return 0;
983983
}
984984
else if (MSGIDLIMIT_AUTH(msg)) {
985-
/* Do not accept any userauth messages until we've asked for auth. */
986-
if (ssh->connectState < CONNECT_CLIENT_USERAUTH_REQUEST_SENT) {
985+
/* No userauth message, the banner included, before our first
986+
* request. wolfSSH_connect() sends it before reading past the
987+
* service accept. */
988+
if (ssh->connectState < CONNECT_CLIENT_USERAUTH_SENT) {
987989
WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by %s %s",
988990
msg, "client", "before sending userauth request");
989991
ssh->error = WS_MSGID_NOT_ALLOWED_E;

‎tests/regress.c‎

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

28102810

2811+
/* Until the first userauth request goes out, no userauth message may
2812+
* arrive, the banner included. */
2813+
static void TestClientUserauthReplyBeforeRequest(WOLFSSH* ssh)
2814+
{
2815+
static const byte msgs[] = {
2816+
MSGID_USERAUTH_FAILURE,
2817+
MSGID_USERAUTH_SUCCESS,
2818+
MSGID_USERAUTH_BANNER,
2819+
MSGID_USERAUTH_PK_OK, /* also INFO_REQUEST and PW_CHRQ */
2820+
};
2821+
static const byte states[] = {
2822+
CONNECT_KEYED,
2823+
CONNECT_CLIENT_USERAUTH_REQUEST_SENT,
2824+
CONNECT_SERVER_USERAUTH_REQUEST_DONE,
2825+
};
2826+
word32 i, j;
2827+
int allowed;
2828+
2829+
for (i = 0; i < sizeof(states)/sizeof(*states); i++) {
2830+
for (j = 0; j < sizeof(msgs)/sizeof(*msgs); j++) {
2831+
ResetSession(ssh);
2832+
ssh->connectState = states[i];
2833+
allowed = wolfSSH_TestIsMessageAllowed(ssh, msgs[j],
2834+
WS_MSG_RECV);
2835+
AssertFalse(allowed);
2836+
AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E);
2837+
}
2838+
}
2839+
2840+
/* The service accept still answers the service request. */
2841+
ResetSession(ssh);
2842+
ssh->connectState = CONNECT_CLIENT_USERAUTH_REQUEST_SENT;
2843+
allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_SERVICE_ACCEPT,
2844+
WS_MSG_RECV);
2845+
AssertTrue(allowed);
2846+
2847+
/* Once the first userauth request is out, all of them are allowed. */
2848+
for (j = 0; j < sizeof(msgs)/sizeof(*msgs); j++) {
2849+
ResetSession(ssh);
2850+
ssh->connectState = CONNECT_CLIENT_USERAUTH_SENT;
2851+
allowed = wolfSSH_TestIsMessageAllowed(ssh, msgs[j],
2852+
WS_MSG_RECV);
2853+
AssertTrue(allowed);
2854+
}
2855+
}
2856+
2857+
2858+
/* A userauth reply to the service request draws a disconnect and leaves
2859+
* serverState alone. */
2860+
static void TestClientUnsolicitedUserauthReplyRejected(byte msgId)
2861+
{
2862+
WOLFSSH_CTX* ctx;
2863+
WOLFSSH* ssh;
2864+
MemIo io;
2865+
byte pkt[32];
2866+
byte out[256];
2867+
word32 pktSz;
2868+
2869+
pktSz = BuildPacket(msgId, pkt, sizeof(pkt));
2870+
2871+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
2872+
AssertNotNull(ctx);
2873+
wolfSSH_SetIORecv(ctx, MemRecv);
2874+
wolfSSH_SetIOSend(ctx, MemSend);
2875+
2876+
ssh = wolfSSH_new(ctx);
2877+
AssertNotNull(ssh);
2878+
2879+
MemIoInit(&io, pkt, pktSz, out, sizeof(out));
2880+
wolfSSH_SetIOReadCtx(ssh, &io);
2881+
wolfSSH_SetIOWriteCtx(ssh, &io);
2882+
2883+
ssh->connectState = CONNECT_CLIENT_USERAUTH_REQUEST_SENT;
2884+
ssh->serverState = SERVER_KEXINIT_DONE;
2885+
2886+
AssertIntEQ(wolfSSH_TestDoReceive(ssh), WS_FATAL_ERROR);
2887+
AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E);
2888+
AssertIntEQ(ssh->serverState, SERVER_KEXINIT_DONE);
2889+
AssertTrue(io.outSz > 0);
2890+
AssertIntEQ(ParseMsgId(io.out, io.outSz), MSGID_DISCONNECT);
2891+
AssertTrue(ssh->disconnected);
2892+
2893+
wolfSSH_free(ssh);
2894+
wolfSSH_CTX_free(ctx);
2895+
}
2896+
2897+
2898+
/* Unused by a "none" request, which still needs a callback. */
2899+
static int NoneUserAuthCb(byte authType, WS_UserAuthData* authData,
2900+
void* ctx)
2901+
{
2902+
(void)authType;
2903+
(void)authData;
2904+
(void)ctx;
2905+
return WOLFSSH_USERAUTH_INVALID_AUTHTYPE;
2906+
}
2907+
2908+
/* A banner bundled behind the service accept, as wolfSSH's server sends
2909+
* it, is read after wolfSSH_connect() sends the first request. */
2910+
static void TestClientBannerBehindServiceAccept(void)
2911+
{
2912+
ChannelOpenHarness harness;
2913+
byte in[128];
2914+
byte payload[64];
2915+
word32 inSz;
2916+
word32 payloadSz;
2917+
2918+
payloadSz = AppendString(payload, sizeof(payload), 0, "ssh-userauth");
2919+
inSz = WrapPacket(MSGID_SERVICE_ACCEPT, payload, payloadSz,
2920+
in, sizeof(in));
2921+
payloadSz = AppendString(payload, sizeof(payload), 0, "banner");
2922+
payloadSz = AppendString(payload, sizeof(payload), payloadSz, "");
2923+
inSz += WrapPacket(MSGID_USERAUTH_BANNER, payload, payloadSz,
2924+
in + inSz, sizeof(in) - inSz);
2925+
2926+
InitChannelOpenHarnessClient(&harness, in, inSz);
2927+
wolfSSH_SetUserAuth(harness.ctx, NoneUserAuthCb);
2928+
AssertIntEQ(wolfSSH_SetUsername(harness.ssh, "user"), WS_SUCCESS);
2929+
harness.ssh->connectState = CONNECT_CLIENT_USERAUTH_REQUEST_SENT;
2930+
harness.ssh->serverState = SERVER_KEXINIT_DONE;
2931+
2932+
/* Both packets read, then WANT_READ for the auth answer. */
2933+
AssertIntEQ(wolfSSH_connect(harness.ssh), WS_FATAL_ERROR);
2934+
AssertIntEQ(wolfSSH_get_error(harness.ssh), WS_WANT_READ);
2935+
AssertIntEQ(harness.io.inOff, inSz);
2936+
AssertIntEQ(harness.ssh->connectState, CONNECT_CLIENT_USERAUTH_SENT);
2937+
AssertIntEQ(harness.ssh->serverState, SERVER_USERAUTH_REQUEST_DONE);
2938+
AssertFalse(harness.ssh->disconnected);
2939+
/* Only the userauth request went out. */
2940+
AssertTrue(harness.io.outSz > 0);
2941+
AssertIntEQ(ParseMsgId(harness.io.out, harness.io.outSz),
2942+
MSGID_USERAUTH_REQUEST);
2943+
AssertIntEQ(NextPacketOffset(harness.io.out, harness.io.outSz),
2944+
harness.io.outSz);
2945+
2946+
FreeChannelOpenHarness(&harness);
2947+
}
2948+
2949+
28112950
/* Drive the whole receive path with a CHANNEL_OPEN sent from a pre-auth
28122951
* connectState: no channel created, and the only thing sent back is the
28132952
* disconnect RFC 4252 section 6 asks for. The connectState gate does the
@@ -17083,6 +17222,14 @@ int main(int argc, char** argv)
1708317222
TestChannelAllowedAfterAuth(ssh);
1708417223
TestClientOnlyKexMsgsBlocked(ssh);
1708517224
TestClientServiceAcceptBlockedDuringKeying(ssh);
17225+
TestClientUserauthReplyBeforeRequest(ssh);
17226+
TestClientUnsolicitedUserauthReplyRejected(MSGID_USERAUTH_SUCCESS);
17227+
TestClientUnsolicitedUserauthReplyRejected(MSGID_USERAUTH_FAILURE);
17228+
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
17229+
/* Id 60 is only a known id as INFO_REQUEST. */
17230+
TestClientUnsolicitedUserauthReplyRejected(MSGID_USERAUTH_PK_OK);
17231+
#endif
17232+
TestClientBannerBehindServiceAccept();
1708617233
TestChannelOpenRejectedBeforeKex(CONNECT_CLIENT_KEXINIT_SENT);
1708717234
TestChannelOpenRejectedBeforeKex(CONNECT_CLIENT_KEXDH_INIT_SENT);
1708817235
TestSessionOnClientSendsOpenFail();

0 commit comments

Comments
 (0)