TLS computational DoS mitigation

Vincent Bernat

Some days ago, a hacker group, THC, re­leased a de­nial of ser­vice tool for TLS web servers. As stated in its de­scrip­tion, the prob­lem is not re­ally new: a com­plete TLS hand­shake im­plies costly cryp­to­graphic com­pu­tations.

There are two dif­fer­ent as­pects in the pre­sented attack:

  • The com­pu­ta­tion cost of a hand­shake is more im­por­tant for the server than for the client. The ad­vi­sory ex­plains that a server will re­quire 15 times the pro­cess­ing power of a client. This means a sin­gle av­er­age work­sta­tion could chal­lenge a multi-core high-end server.
  • TLS rene­go­ti­a­tion al­lows you to trig­ger hun­dreds of hand­shakes in the same TCP con­nec­tion. Even a client be­hind a DSL con­nec­tion can there­fore bomb a server with a lot of rene­go­ti­a­tion requests.

Up­date (2015-02)

While the con­tent of this ar­ti­cle is still tech­ni­cally sound, en­sure you un­der­stand it was writ­ten by the end of 2011 and there­fore doesn’t take into ac­count many im­por­tant as­pects, like the fall of RC4 as an ap­pro­pri­ate cipher.

Mitigation techniques#

There is no de­fin­i­tive so­lu­tion to this at­tack but there ex­ists some work­arounds. Since the DoS tool from THC re­lies heav­ily on rene­go­ti­a­tion, the most ob­vi­ous one is to dis­able this mech­a­nism on the server side but we will ex­plore other pos­si­bilities.

Disabling TLS renegotiation#

Tack­ling the sec­ond prob­lem seems easy: just dis­able TLS rene­go­ti­a­tion. It is hardly needed: a server can trig­ger a rene­go­ti­a­tion to ask a client to present a cer­tifi­cate but a client usu­ally does not have any rea­son to trig­ger one. Be­cause of a past vul­ner­a­bil­ity in TLS rene­go­ti­a­tion, re­cent ver­sions of Apache and ng­inx just for­bid it, even when the non-vul­ner­a­ble ver­sion is available.

openssl s_client can be used to test if TLS rene­go­ti­a­tion is re­ally dis­abled. Send­ing R on an empty line trig­gers rene­go­ti­a­tion. Here is an ex­am­ple where rene­go­ti­a­tion is dis­abled (de­spite be­ing ad­ver­tised as sup­ported):

$ openssl s_client -connect www.luffy.cx:443 -tls1
[…]
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: zlib compression
Expansion: zlib compression
[…]
R
RENEGOTIATING
140675659794088:error:1409E0E5:SSL routines:SSL3_WRITE_BYTES:ssl handshake failure:s3_pkt.c:591:

Dis­abling rene­go­ti­a­tion is not triv­ial with OpenSSL. As an ex­am­ple, I have pushed a patch to dis­able rene­go­ti­a­tion in stud, the scal­able TLS un­wrap­ping daemon.

Rate limiting TLS handshakes#

Dis­abling TLS rene­go­ti­a­tion on the client side is not al­ways pos­si­ble. For ex­am­ple, your web server may be too old to pro­pose such an op­tion. Since these rene­go­ti­a­tions should not hap­pen of­ten, a work­around is to limit them.

When the flaw was first ad­ver­tised, F5 Net­works pro­vided a way to con­fig­ure such a lim­i­ta­tion with an iRule on their load-bal­ancers. We can do some­thing sim­i­lar with Net­fil­ter. We can spot most TCP pack­ets trig­ger­ing such a rene­go­ti­a­tion by look­ing for en­crypted TLS hand­shake record. They may hap­pen in a reg­u­lar hand­shake but in this case, they usu­ally are not at the be­gin­ning of the TCP pay­load. There is no field say­ing if a TLS record is en­crypted or not (TLS is state­ful for this pur­pose). There­fore, we have to use some heuris­tics. If the hand­shake type is un­known, we as­sume that this is an en­crypted record. More­over, rene­go­ti­a­tion re­quests are usu­ally en­cap­su­lated in a TCP packet flagged with “push.”

# Access to TCP payload (if not fragmented)
payload="0 >> 22 & 0x3C @ 12 >> 26 & 0x3C @"
iptables -A LIMIT_RENEGOTIATION \
    -p tcp --dport 443 \
    --tcp-flags SYN,FIN,RST,PSH PSH \
    -m u32 \
    --u32 "$payload 0 >> 8 = 0x160300:0x160303 && $payload 2 & 0xFF = 3:10,17:19,21:255" \
    -m hashlimit \
    --hashlimit-above 5/minute --hashlimit-burst 3 \
    --hashlimit-mode srcip --hashlimit-name ssl-reneg \
    -j DROP

The use of u32 match is a bit dif­fi­cult to read. The man­ual page gives some in­sight­ful ex­am­ples. $payload al­lows us to seek for the TCP pay­load. It only works if there is no frag­men­ta­tion. Then, we check if we have a hand­shake (0x16) and if we recog­nise TLS ver­sion (0x0300, 0x0301, 0x0302 or 0x0303). At last, we check if the hand­shake type is not a known value.

There is a risk of false pos­i­tives but since we use hashlimit, we should be safe. This is not a bul­let­proof so­lu­tion: TCP frag­men­ta­tion would al­low an at­tacker to evade de­tec­tion. An­other equiv­a­lent so­lu­tion would be to use CONNMARK to record the fact the ini­tial hand­shake has been done and for­bid any sub­se­quent hand­shakes.1

If you hap­pen to dis­able TLS rene­go­ti­a­tion, you can still use some Net­fil­ter rule to limit the num­ber of TLS hand­shakes by lim­it­ing the num­ber of TCP con­nec­tions from one IP:

iptables -A LIMIT_TLS \
    -p tcp --dport 443 \
    --syn -m state --state NEW \
    -m hashlimit \
    --hashlimit-above 120/minute --hashlimit-burst 20 \
    --hashlimit-mode srcip --hashlimit-name ssl-conn \
    -j DROP

Your servers will still be vul­ner­a­ble to a large bot­net but if there is only a hand­ful of source IP, this rule will work just fine.2

I have made all these so­lu­tions avail­able in a sin­gle file.

Increasing server-side processing power#

TLS can eas­ily be scaled up and out. Since TLS per­for­mance in­creases lin­early with the num­ber of cores, scal­ing up can be done by throw­ing in more CPU or more cores per CPU. Adding ex­pen­sive TLS ac­cel­er­a­tors would also do the trick. Scal­ing out is also rel­a­tively easy but you should care about TLS ses­sion resume.

Putting more work on the client#

In their pre­sen­ta­tion of the de­nial of ser­vice tool, THC explains:

Es­tab­lish­ing a se­cure SSL con­nec­tion re­quires 15× more pro­cess­ing power on the server than on the client.

I don’t know where this fig­ure comes from. To check it, I built a small tool to mea­sure CPU time of a client and a server do­ing 1000 hand­shakes with var­i­ous pa­ram­e­ters (ci­pher suites and key sizes). The re­sults are sum­ma­rized on the fol­low­ing plot:

Plot to compare computational power required by servers and clients
Comparison of computational power needed by servers and clients

Up­date (2011-11)

Adam Lan­g­ley an­nounced Google HTTPS sites now sup­port for­ward se­crecy and ECDHE-RSA-RC4-SHA is now the pre­ferred ci­pher suite thanks to fast, con­stant-time im­ple­men­ta­tions of el­lip­tic curves P-224, P-256 and P-521 in OpenSSL. The tests above did not use these im­ple­men­tations.

For ex­am­ple, with 2048-bit RSA cer­tifi­cates and a ci­pher suite like AES256-SHA, the server needs 6 times more CPU power than the client. How­ever, if we use DHE-RSA-AES256-SHA in­stead, the server needs 34% less CPU power. The most ef­fi­cient ci­pher suite from the server point of view seems to be some­thing like DHE-DSS-AES256-SHA where the server needs half the power of the client.

How­ever, you can’t re­ally use only these shiny ci­pher suites:

  1. Some browsers do not sup­port them: they are lim­ited to RSA ci­pher suites.3
  2. Us­ing them will in­crease your reg­u­lar load a lot. Your servers may col­lapse with just le­git­i­mate traffic.
  3. They are ex­pen­sive for some mo­bile clients: they need more mem­ory, more pro­cess­ing power and will drain bat­tery faster.

Let’s dig a bit more into why the server needs more com­pu­ta­tional power in the case of RSA. Here is a TLS hand­shake when us­ing a ci­pher suite like AES256-SHA:

TLS full handshake
Full TLS handshake

When send­ing the Client Key Ex­change mes­sage, the client will en­crypt TLS ver­sion and 46 ran­dom bytes with the pub­lic key of the cer­tifi­cate sent by the server in its Cer­tifi­cate mes­sage. The server will have to de­crypt this mes­sage with its pri­vate key. These are the two most ex­pen­sive op­er­a­tions in the hand­shake. En­cryp­tion and de­cryp­tion are done with RSA (be­cause of the se­lected ci­pher suite). To un­der­stand why de­cryp­tion is more ex­pen­sive than en­cryp­tion, let me ex­plain how RSA works.

First, the server needs a pub­lic and a pri­vate key. Here are the main steps to gen­er­ate them:

  1. Pick two ran­dom dis­tinct prime num­bers pp and qq, each roughly the same size.
  2. Com­pute n=pqn=pq. It is the mod­u­lus.
  3. Com­pute φ(n)=(p−1)(q−1)\varphi(n)=(p-1)(q-1).
  4. Choose an in­te­ger ee such that 1<e<φ(n)1<e<\varphi(n) and gcd⁡(φ(n),e)=1\gcd(\varphi(n),e) = 1 (i.e. ee and φ(n)\varphi(n) are co­prime). It is the pub­lic ex­po­nent.
  5. Com­pute d=e−1 mod φ(n)d=e^{-1}\bmod\varphi(n). It is the pri­vate key ex­po­nent.

The pub­lic key is (n,e)(n,e) while the pri­vate key is (n,d)(n,d). A mes­sage to be en­crypted is first turned into an in­te­ger m<nm<n (with some ap­pro­pri­ate pad­ding). It is then en­crypted to a ci­phered mes­sage cc with the pub­lic key and should only be de­crypted with the pri­vate key:

  • c=me mod nc=m^e\bmod n  (en­cryp­tion)
  • m=cd mod nm=c^d\bmod n  (de­cryp­tion)

So, why is de­cryp­tion more ex­pen­sive? In fact, the key pair is not re­ally gen­er­ated as I de­scribed above. Usu­ally, ee is a small fixed prime num­ber with a lot of 0, like 17 (0x11) or 65537 (0x10001) and pp and qq are cho­sen such that φ(n)\varphi(n) is co­prime with ee. This al­lows en­cryp­tion to be fast us­ing ex­po­nen­ti­a­tion by squar­ing. On the other hand, its in­verse dd is a big num­ber with no spe­cial prop­erty and there­fore, ex­po­nen­ti­a­tion is more costly and slow.

In­stead of com­put­ing dd from ee, it is pos­si­ble to choose dd and com­pute ee. We could choose dd to be small and co­prime with φ(n)\varphi(n) and then com­pute e=d−1 mod φ(n)e=d^{-1}\bmod\varphi(n) and get blaz­ingly fast de­cryp­tion. Un­for­tu­nately, there are two prob­lems with this:

There­fore, we can­not use a small pri­vate ex­po­nent. The best we can do is to choose the pub­lic ex­po­nent to be e′=4294967291e'=4294967291 (the big­gest prime 32-bit num­ber and it con­tains only one 0). How­ever, there is no change as you can see on our com­par­a­tive plot.

To sum­ma­rize, no real so­lu­tion here. You need to al­low RSA ci­pher suites and there is no way to im­prove the com­pu­ta­tional ra­tio be­tween the server and the client with such a ci­pher suite.

Things get worse#

Shortly af­ter the re­lease of the de­nial of ser­vice tool, Eric Rescorla4 pub­lished a good anal­y­sis on the im­pact of such a tool. He asks him­self about the ef­fi­ciency to use rene­go­ti­a­tion for such an attack:

What you should be ask­ing at this point is whether a com­pu­ta­tional DoS at­tack based on rene­go­ti­a­tion is any bet­ter for the at­tacker than a com­pu­ta­tional DoS at­tack based on mul­ti­ple con­nec­tions. The way we mea­sure this is by the ra­tio of the work the at­tacker has to do to the work that the server has to do. I’ve never seen any ac­tual mea­sure­ments here (and the THC guys don’t present any), but some back of the en­ve­lope cal­cu­la­tions sug­gest that the dif­fer­ence is small.

If I want to mount the old, mul­ti­ple con­nec­tion at­tack, I need to in­cur the fol­low­ing costs:

  1. Do the TCP hand­shake (3 packets)
  2. Send the SSL/TLS ClientHello (1 packet). This can be a canned message.
  3. Send the SSL/TLS ClientKeyExchange, ChangeCipherSpec, Fin­ished mes­sages (1 packet). These can also be canned.

Note that I don’t need to parse any SSL/TLS mes­sages from the server, and I don’t need to do any cryp­tog­ra­phy. I’m just go­ing to send the server junk any­way, so I can (for in­stance) send the same bo­gus ClientKeyExchange and Fin­ished ev­ery time. The server can’t find out that they are bo­gus un­til it’s done the ex­pen­sive part. So, roughly speak­ing, this at­tack con­sists of send­ing a bunch of canned pack­ets in or­der to force the server to do one RSA de­cryption.

I have writ­ten a quick proof of con­cept of such a tool. To avoid any abuse, it will only work if the server sup­ports NULL-MD5 ci­pher suite. No sane server in the wild will sup­port such a ci­pher. You need to con­fig­ure your web server to sup­port it be­fore us­ing this tool.

While Eric ex­plains that there is no need to parse any TLS mes­sages, I have found that if the key ex­change mes­sage is sent be­fore the server sends the an­swer, the con­nec­tion will be aborted. There­fore, I quickly parse the server’s an­swer to check if I can con­tinue. Eric also says a bo­gus key ex­change mes­sage can be sent since the server will have to de­crypt it be­fore dis­cov­er­ing it is bo­gus. I have cho­sen to build a valid key ex­change mes­sage dur­ing the first hand­shake (us­ing the cer­tifi­cate pre­sented by the server) and re­play it on sub­se­quent hand­shakes be­cause I think the server may dis­miss the mes­sage be­fore the com­pu­ta­tion is com­plete (for ex­am­ple, if the size does not match the size of the cer­tificate).

Up­date (2011-11)

Michał Tro­j­nara has writ­ten sslsqueeze, a sim­i­lar tool. It uses libevent2 li­brary and should dis­play bet­ter per­for­mance than mine. It does not com­pute a valid key ex­change mes­sage but en­sures the length is correct.

With such a tool and 2048-bit RSA cer­tifi­cate, a server is us­ing 100 times more pro­cess­ing power than the client. Un­for­tu­nately, this means that most so­lu­tions, ex­cept rate lim­it­ing, ex­posed on this page may just be in­ef­fective.